Compare commits

..
384 Commits
Author SHA1 Message Date
thomasandClaude Opus 4.8 e28d3bf53d admin: Remove default Chinese welcome message
Build & Push Docker Image / docker (push) Failing after 13s
Empty conf/admin/hello.html so the admin dashboard no longer shows the
"你好 ... 欢迎使用 RustDesk API" welcome card by default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 13:00:07 +02:00
thomasandClaude Opus 4.8 6dfd40a10f docs: Add self-contained docker-compose.yml.example
Build & Push Docker Image / docker (push) Failing after 13s
Full stack example (hbbs + hbbr + rustdesk-api) with everything inline:
public endpoints, a fixed Ed25519 key pair (secret on the servers, public
on the API) and the JWT key. No .env required.

The included key is an example and should be regenerated for production.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 12:46:40 +02:00
thomasandClaude Opus 4.8 05f919777f config: Default language to English (en)
Build & Push Docker Image / docker (push) Failing after 13s
Change the default app language from zh-CN to en so that the i18n-seeded
default group names ("Default Group"/"Share Group" instead of 默认组/共享组)
and all backend i18n responses default to English.

Note: existing databases already seeded under zh-CN keep their Chinese
group names and must be renamed manually.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 12:33:05 +02:00
thomasandClaude Opus 4.8 0735892b46 ci: Publish Docker image to Gitea registry only
Build & Push Docker Image / docker (push) Failing after 2m34s
Add .gitea/workflows/docker.yml that builds the image via Dockerfile.dev
and pushes it to the Gitea container registry at
git.hackner.dev/thomas/rustdesk-api on pushes to master and on v*.*.* tags.

Remove the GitHub Actions workflows (build.yml, build_test.yml): they push
to Docker Hub and GHCR (not wanted) and rely on GitHub-only features
(GITHUB_TOKEN, GitHub Releases, changelogithub) that do not work on Gitea.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 12:23:06 +02:00
thomasandClaude Opus 4.8 16e97c8efa i18n: Translate all Chinese to English across the codebase
Translate Chinese comments, log/error messages, validator labels and
Swagger annotations to English throughout the source code, generated
Swagger docs, config files and CI workflows.

Make the English README primary: README.md now holds the English docs
and README_EN.md holds the Chinese version, with cross-language links
updated accordingly.

Note: the generated docs/ swagger files were translated in place; run
`go generate ./...` (swag init) to regenerate them from the now-English
annotations when a Go toolchain is available.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 12:17:04 +02:00
lejianwen c5687e1506 docs: Removed webclient2 because of DMCA 2025-09-29 09:41:07 +08:00
lejianwen 222792419f feat!: Removed webclient2 because of DMCA 2025-09-28 20:08:28 +08:00
lejianwen 04789e08d5 feat!: Removed webclient2 because of DMCA 2025-09-27 11:30:37 +08:00
lejianwen 929f6ddf20 feat: WebClient 1.4.2 2025-09-12 15:11:49 +08:00
nomoneynolife 6e3b16d425 feat:ldap allow-group (#388) 2025-09-05 12:53:45 +08:00
Tom ce0291a8f9 feat: add TLS configuration option for MySQL (#384) 2025-09-03 19:43:16 +08:00
lejianwen faee954f6d docs: readme 2025-09-01 21:36:27 +08:00
lejianwen f09c898f16 feat(peer): add alias field and support filtering by alias 2025-08-31 13:39:22 +08:00
lejianwen a7c087afbb fix!: Update peer to use ID instead of UUID 2025-08-31 12:46:54 +08:00
lejianwen 07dfba9d59 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 448725b760 fix: Normal user can not change the name of their own address book (#341) 2025-08-10 11:17:51 +08:00
Tao Chen b6be4dea21 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 862a1d431e docs: README_EN.md (#340) 2025-07-31 10:42:56 +08:00
startgo 8819c85ef4 feat: Update zh_TW.toml (#322)
Corrected translation to match Taiwanese Traditional Chinese usage
2025-07-19 21:08:40 +08:00
lejianwen e2ec387ba2 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 1d2d1d4c98 fix: Oauth callback url is fixed to host+/api/oidc/callback (#314) 2025-07-11 09:55:48 +08:00
Plynksiy Nikita 16b941e37f 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 ff2c79a316 feat(user): add remark field to User model and form (#307) 2025-07-08 12:14:46 +08:00
lejianwen b3aee87655 feat: Update database version constant to 264 2025-07-08 12:13:31 +08:00
k3-cat 73a8461a2d 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 af4813db30 feat: better autorenewal (#305) 2025-07-04 14:27:47 +08:00
Plynksiy Nikita 9d2b589faa 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 aa04b225b9 feat: Use crypto/rand for secure random string generation (#293) 2025-06-18 20:47:24 +08:00
Plynksiy Nikita fb34d0ac83 fix: correct typo in build arg FRONTEND_GIT_REPO (#292) 2025-06-18 20:42:13 +08:00
Plynksiy Nikita f68ec8f3a1 feat(i18n): replace hardcoded messages with translated strings (#289) 2025-06-17 09:05:10 +08:00
lejianwen 4b16ad29bb chore: Update Go version to 1.23 in build configurations 2025-06-16 15:41:16 +08:00
lejianwen c554ff4a31 docs: Update swagger docs 2025-06-16 12:31:48 +08:00
lejianwen 791a1850b8 feat: Add PostgreSQL support and refactor MySQL DSN handling (#284) 2025-06-16 12:26:08 +08:00
lejianwen 175fdc3bd4 feat: Add start time in /api/sysinfover 2025-06-16 12:23:48 +08:00
lejianwen f20414b3a5 fix(admin): Use admin-hello first
(#274) (#255)
2025-06-15 15:33:12 +08:00
puyujian bf39a29e6c feat(oauth): 支持linux.do登录 (#280)
* 支持linux.do登录

* 修正
2025-06-15 15:32:20 +08:00
lejianwen 4ba805ac5f chore: update download links for musl cross-compilers 2025-06-05 12:14:17 +08:00
lejianwen b2d71d4312 fix: Init sqlite fail(#266) 2025-06-04 09:31:43 +08:00
lejianwen a35fa6f9a8 fix(server): Port custom (#257) 2025-05-30 12:27:37 +08:00
lejianwen 83baa140f5 fix: Captcha some problem when users login with same ip 2025-05-27 17:36:20 +08:00
lejianwen b6eaa4b061 docs: Readme 2025-05-25 17:44:29 +08:00
lejianwen d4fdf3785b feat(register): Register status can be set (#223) 2025-05-25 17:03:13 +08:00
lejianwen f2ea022965 feat(login): Captcha upgrade and add the function to ban IP addresses (#250) 2025-05-25 16:52:58 +08:00
Tao Chen fe4a115c9d fix: dn should be case-insensitive (#250) 2025-05-21 09:07:08 +08:00
lejianwen 4412f0309c feat(webclient): Web client to 1.4.0 2025-05-12 20:16:08 +08:00
lejianwen dd78911ed9 fix: PageSize (#225) 2025-05-06 19:08:18 +08:00
lejianwen b23a9bde8e chore: Noelware/docker-manifest-action 2025-04-25 16:20:36 +08:00
lejianwen 3fe7184b4b chore: Noelware/docker-manifest-action 2025-04-25 14:34:45 +08:00
lejianwen 4637879091 style: Oauth page languages 2025-04-24 21:52:43 +08:00
lejianwen 6d1184d332 style: Remove useless configurations 2025-04-15 10:52:46 +08:00
lejianwen fb8ef8a1fd feat: Peer share to group 2025-04-14 19:12:40 +08:00
lejianwen ae59b6a733 feat: Add SysInfoVer endpoint and AppService for version retrieval 2025-04-07 16:38:21 +08:00
lejianwen 605e88e04c fix: Update peer based on the UUID (#180) 2025-04-02 09:50:16 +08:00
lejianwen 3ef4454c05 feat: Add Korean translations validator (#168) 2025-04-02 09:42:29 +08:00
lejianwen 35be09b37c fix: Get Uuids 2025-03-15 21:02:47 +08:00
lejianwen 891469909c style: Update peer last online time logic (#173) 2025-03-15 21:02:08 +08:00
lejianwen 6ed75bc517 feat(admin): Add filter by ip and username (#172) 2025-03-15 19:49:49 +08:00
Tao Chen caea60c732 fix: rm varify password accidentally (#176) 2025-03-15 19:40:02 +08:00
lejianwen 0a78c206de fix: Init database err (#166) 2025-03-04 18:14:25 +08:00
lejianwen a7f7c76e0e docs: Readme 2025-03-04 16:30:12 +08:00
lejianwen c10d132b3c fix: templates 2025-03-04 16:14:34 +08:00
lejianwen 8b951065ce style(webclient): ws-host 2025-03-04 15:48:25 +08:00
lejianwen 08420f9db6 feat(config): add ws-host configuration (#156) 2025-03-04 15:43:25 +08:00
Tao Chen 5db3c8bf0b fix: Fix/ldap tls (#162)
* optimize and fix tls of LDAP

* fix
2025-03-02 22:59:01 +08:00
lejianwen 403d9c2233 style(service): refactor global dependencies to use local variables 2025-02-26 19:15:38 +08:00
lejianwen 0f16f61ab3 feat(oauth): Oauth nonce (#148) 2025-02-26 16:36:53 +08:00
lejianwen 0dd92311b2 feat(oauth): Oauth callback page beautification (#115) 2025-02-25 13:51:49 +08:00
lejianwen ef4af39f94 feat(api): Add device group for 1.3.8 2025-02-23 21:35:42 +08:00
lejianwen 73f3056878 feat(api): Add /device-group/accessible for 1.3.8 2025-02-23 15:32:44 +08:00
lejianwen f0b4b0d7c6 style(oidc): Oidc style 2025-02-21 09:49:41 +08:00
lejianwen 5d5aae6b3c fix(admin): Admin hello 2025-02-20 19:37:34 +08:00
lejianwen 4b5e316475 style: No need exec sql on version 261 2025-02-20 19:22:22 +08:00
lejianwen 8ff9a12a34 fix: Js content-type 2025-02-19 16:04:03 +08:00
Tao Chen 6f55c5b642 feat(oidc): add pkce (#150) 2025-02-19 09:31:25 +08:00
lejianwen 99e63cadcf docs: Readme 2025-02-17 10:59:38 +08:00
lejianwen c0346ff9e1 fix(config)!: Token expire time (#145)
将配置中的过期时间单位统一为time.Duration,可以设置为`h`,`m`,`s`
2025-02-17 10:49:59 +08:00
lejianwen d85a00f748 docs: Readme 2025-02-16 21:06:55 +08:00
lejianwen 6a6a1ed02b feat: Login by pwd can be disable
---

Closes: #141
2025-02-16 13:06:45 +08:00
lejianwen 4b3bbe809d style: middleware name 2025-02-12 22:09:52 +08:00
lejianwen 63cf231bdc fix(admin): Admin web title 2025-02-12 21:10:07 +08:00
lejianwen 6aebf061a4 style: Module name 2025-02-12 19:46:39 +08:00
lejianwen a2672c9489 style: generate 2025-02-12 16:56:41 +08:00
lejianwen a304da6e02 style: Remove generate 2025-02-12 16:25:04 +08:00
lejianwen 0e160f110b docs: Docs 2025-02-12 16:14:51 +08:00
lejianwen 32a9eda8ca style: Module name 2025-02-12 16:07:51 +08:00
lejianwen 8622c78da0 fix: Config watchConfig (#135)
---
Closes: #135
2025-02-10 10:13:34 +08:00
lejianwen 7d6bc2a74f fix: User disabled can not work (#133)
---
Closes: #133
2025-02-10 10:13:15 +08:00
Tao Chen 5224c31d61 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 983025538f docs: Readme 2025-02-09 21:13:01 +08:00
Tao Chen cd5b247356 docs: add LDAP info (#130) 2025-02-09 19:36:31 +08:00
lejianwen e2d72530b1 docs: Readme 2025-02-07 18:14:12 +08:00
lejianwen 5660d40514 style: Log time 2025-02-07 17:57:28 +08:00
lejianwen d486444680 feat: Web sso env (#125) 2025-02-07 17:52:40 +08:00
lejianwen 2261a4292a docs: Up readme 2025-02-07 17:52:40 +08:00
lejianwen 829f8799dd style: Up conf 2025-02-07 17:52:40 +08:00
lejianwen e549416d94 docs: Up readme 2025-02-07 17:52:40 +08:00
lejianwen ff77014acb feat: Random Initial Password for Admin (#117) 2025-02-07 17:52:39 +08:00
lejianwen daab7112be fix(build): Fix no admin in deb (#119 #120) 2025-02-03 13:29:35 +08:00
lejianwen 12442f1ba0 fix(api): Add Default Token Expire (#113) 2025-02-03 13:18:08 +08:00
Tao Chen 0e3f204327 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 d9fcfdde6a feat(api): Add api token expire
Resolves #109
2025-01-21 18:23:28 +08:00
lejianwen 44fa22b7e5 feat(api): Add api/version
Resolves #110
2025-01-20 20:04:22 +08:00
lejianwen 59038e27f8 docs: Up Swagger docs 2025-01-20 19:35:47 +08:00
lejianwen 7d23ce3caf style: Add Start Tips 2025-01-20 13:12:45 +08:00
lejianwen 7c528facf9 fix(api): Change tag to alphabetical sorting
Fixes: #108
2025-01-20 13:07:41 +08:00
lejianwen f8548a6589 fix(docs): Api Route doc 2025-01-19 13:10:43 +08:00
lejianwen 23c5a96fe1 feat(i18n): Add ZH_TW 2025-01-19 13:10:19 +08:00
Jia-Bin c72865b181 Add Traditional Chinese 2025-01-18 23:04:39 +08:00
lejianwen 9a2f7010e1 docs: Up readme 2025-01-16 22:01:23 +08:00
lejianwen a72096b56d docs: Up readme 2025-01-16 21:59:46 +08:00
lejianwen 56014d35bd feat(server): Rustdesk Id Server Port & Relay Server Port #104 2025-01-16 20:57:00 +08:00
lejianwen a7e4ce4f72 refactor(config): Up Config Load 2025-01-16 20:40:42 +08:00
lejianwen f2cf3e1060 docs: Up readme 2025-01-15 21:56:04 +08:00
lejianwen a4433e6113 fix: Jwt 2025-01-15 20:26:26 +08:00
lejianwen cca191aad3 docs: Up readme 2025-01-15 20:09:08 +08:00
lejianwen edb095ab0b feat!: Add JWT
- `RUSTDESK_API_JWT_KEY`如果设置,将会启用JWT,token自动续期功能将失效
- 此功能是为了server端校验token的合法性
2025-01-15 19:25:28 +08:00
lejianwen ac45a1b5ad docs: Up readme 2025-01-12 23:12:00 +08:00
lejianwen 83279a553a docs: Up readme 2025-01-12 21:35:34 +08:00
lejianwen a5671e692d feat(server): Add Rustdesk Relay Server Commands 2025-01-04 20:49:44 +08:00
lejianwen 63f2fd2f07 style(server): fmt print to log 2025-01-02 21:49:37 +08:00
lejianwen 5a09850fc4 docs: Up readme 2025-01-02 17:03:07 +08:00
lejianwen aac44835ed fix: Fix Dockerfile_full_s6 2024-12-31 23:33:17 +08:00
lejianwen 4eed76d674 fix(server): Fix Rustdesk Sys Command 2024-12-31 23:29:05 +08:00
lejianwen d1fc0036cb feat(server): Add Rustdesk Command
And add build full s6 image for rustdesk command
2024-12-31 23:16:15 +08:00
lejianwen 1555865612 docs(readme): Up readme
Connection timeout issues move to #92
2024-12-30 13:36:57 +08:00
lejianwen 147de065fa docs(readme): Up readme
Connection timeout issues move to #92
2024-12-30 13:35:06 +08:00
lejianwen 8dc5f82a0e docs(readme): Up readme
Connection timeout issues move to #92
2024-12-30 13:33:11 +08:00
lejianwen 3f8cb4dac5 chore(buildTest): add start.bat to run on windows 2024-12-27 20:13:55 +08:00
lejianwen 4b9e1f02f2 fix(build): add start.bat to run on windows(#89) 2024-12-27 20:09:08 +08:00
lejianwen 18312336a4 refactor(admin): Move Admin Web Route to user model 2024-12-27 19:27:33 +08:00
lejianwen 72e3a9a841 feat(admin): Add My Login log 2024-12-27 19:25:59 +08:00
lejianwen 16427164f8 feat(admin): Support Markdown to welcome msg 2024-12-25 19:21:50 +08:00
lejianwen 5c96350e7d fix(api): Get ab list when personal is disabled (#86) 2024-12-25 19:04:42 +08:00
lejianwen 1dd920d192 fix(webclient): share fail when expire is 0
Closes: #88
2024-12-25 15:01:03 +08:00
lejianwen c41038d2d8 fix(build): fix build_test.yml 2024-12-25 14:06:29 +08:00
lejianwen 88ec961312 fix(build): up build.yml to build deb 2024-12-25 13:20:12 +08:00
lejianwen a64b9ced1c chore(build): up build.yml to build deb 2024-12-25 12:57:11 +08:00
Follow the windandymwl c778990cf9 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 ba46dbbf7e chore(changelog): up build.yml to generate changelog 2024-12-22 14:14:51 +08:00
lejianwen 3b8b8f37dd chore(changelog): up build.yml to generate changelog 2024-12-22 14:04:23 +08:00
51db63cf35 feat(i18n): Merge pull request #85 from jimmyGALLAND/trans-fr
add locale french
2024-12-22 13:57:56 +08:00
jimmyGALLAND 9ebfd6de85 add locale french 2024-12-21 22:56:48 +01:00
lejianwen 1c3afa4a70 chore(changelog): up build.yml to add changelog 2024-12-21 21:39:11 +08:00
lejianwen 5cddbf14c8 docs(webclient): up readme 2024-12-21 21:31:54 +08:00
lejianwen 88765302e8 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 034ec6ca1b optimize add ab from peer #84 2024-12-20 11:11:32 +08:00
lejianwen a55e501b50 up docs and readme 2024-12-18 14:28:06 +08:00
lejianwen e36d307d28 fix captcha 2024-12-18 13:51:06 +08:00
lejianwen 1e15287c83 fix captcha 2024-12-18 13:36:01 +08:00
lejianwen c298ddea0c add show-swagger config #83 2024-12-18 12:50:09 +08:00
lejianwen 41fd387baf up docs 2024-12-18 12:44:06 +08:00
lejianwen bf8eadfbaa add captcha #82 2024-12-18 12:43:55 +08:00
lejianwen a3012cfa18 split my from admin 2024-12-17 21:41:56 +08:00
lejianwen 592b04746d add batch add ab from peer and up my 2024-12-13 16:27:12 +08:00
lejianwen ef0410a8b9 add share record manage 2024-12-13 12:32:36 +08:00
lejianwen 3a430c72a2 up README.md 2024-12-10 15:28:05 +08:00
lejianwen 1eaae8f213 up README.md 2024-12-09 13:43:04 +08:00
lejianwen 8c0ac5621a add batch add ab from peer
add batch update ab tags
2024-12-06 19:45:41 +08:00
lejianwen cd74cd19ef up api docs 2024-12-06 10:36:40 +08:00
lejianwen 1c5cd3ae62 add batch delete user token 2024-12-06 10:36:27 +08:00
lejianwen 7749ff0729 up username length to 32 #70 2024-12-06 10:22:28 +08:00
lejianwen b77a104d9c up readme 2024-11-28 12:42:15 +08:00
fc898ba30c Create LICENSE 2024-11-26 17:29:46 +08:00
lejianwen 287bbd43fb add es lang 2024-11-26 10:43:01 +08:00
lejianwen 65571ab2f5 up docs 2024-11-22 19:53:49 +08:00
lejianwen 17b65b412f up img 2024-11-20 19:33:27 +08:00
lejianwen b06d9c4e22 up readme 2024-11-17 18:27:15 +08:00
lejianwen 601d47570e up package lock 2024-11-16 20:56:26 +08:00
lejianwen 29d6e36d50 up ws js 2024-11-16 20:03:30 +08:00
lejianwen abca3a9300 up 2024-11-16 19:42:13 +08:00
lejianwen 22e470fee5 add webclientv2 2024-11-16 18:33:02 +08:00
lejianwen 78869a4a57 fix gitignore 2024-11-15 21:43:23 +08:00
ljw d0f8d79efb up readme 2024-11-12 11:54:21 +08:00
ljw 5417e383c7 up readme 2024-11-12 11:53:57 +08:00
ljw 49eaa831ed add batch delete log #57 2024-11-12 09:08:10 +08:00
ljw ae2e78e373 up admin conf 2024-11-12 08:43:49 +08:00
ljw 7bb2ce0a43 add admin conf 2024-11-11 22:26:15 +08:00
ljw 15a518c1ac up readme 2024-11-09 20:38:11 +08:00
ljw 4915cfa9ba fix #55 2024-11-09 20:14:14 +08:00
ljw de712ba9c5 add build_test.yml 2024-11-08 16:09:50 +08:00
ljw c58f08af27 add build_test.yml 2024-11-08 16:05:43 +08:00
ljw 5609a1a618 add cmd 2024-11-08 15:54:49 +08:00
ljw 70a03f1aba fix #52 & add auto refresh token #53 2024-11-07 10:46:00 +08:00
7c6aaa171e Merge pull request #52 from IamTaoChen/fix/bug
fix: cannot delete user
2024-11-07 10:21:50 +08:00
Tao Chen 34a27ba073 add error information 2024-11-06 15:06:15 +08:00
Tao Chen 956700c27f fix: cannot delete user 2024-11-06 14:36:12 +08:00
ljw 197a718f03 fix migrate 2024-11-05 21:07:39 +08:00
ljw 2c1892f40c fix migrate 2024-11-05 21:07:31 +08:00
ljw 943b1f8077 fix migrate 2024-11-05 21:03:32 +08:00
ljw e71ea2243f fix username length #48 2024-11-05 11:57:16 +08:00
ljw daeae19194 up oauth re 2024-11-05 11:48:35 +08:00
ljw 4321a41cd7 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 3acfb36c5d modify google ro re-use oidc 2024-11-04 21:30:58 +08:00
Tao Chen 5a53f180e4 fix: delete check 2024-11-03 22:23:24 +08:00
Tao Chen ca79a63492 fix: call us.IsAdmin(u) to check admin 2024-11-03 21:59:17 +08:00
Tao Chen 9dfe745629 fix google 2024-11-03 18:04:28 +08:00
Tao Chen 18d59d7047 re-use responseLoginSuccess 2024-11-03 17:25:27 +08:00
Tao Chen aee25a6c99 fix: last admin shouldn't be deleted, disabled or demoted 2024-11-03 17:19:05 +08:00
Tao Chen fea3960672 fix: Github AvatarUrl to OauthUser 2024-11-03 16:49:28 +08:00
Tao Chen da7b70c471 add err for RegisterByOauth 2024-11-03 16:49:03 +08:00
Tao Chen 3cd90c8f74 fronted for docker-dev 2024-11-03 16:34:50 +08:00
Tao Chen fb9173ed53 optimize /admin/login-options 2024-11-03 05:37:34 +08:00
Tao Chen b9efc73025 chroe 2024-11-03 05:34:19 +08:00
Tao Chen 64f28c17d8 add Avatar to OauthUser 2024-11-03 05:33:59 +08:00
Tao Chen 1ca50b5e9d low case email 2024-11-03 05:25:10 +08:00
Tao Chen 9ed376715f const var for op name 2024-11-03 05:13:22 +08:00
Tao Chen 6698877761 fix: email from github 2024-11-03 05:11:31 +08:00
Tao Chen 2ceaa0091b fix: Email of Register 2024-11-03 05:07:17 +08:00
Tao Chen 6fb1fbc5b1 fix: RegisterByOauth without Email 2024-11-03 04:35:39 +08:00
ljw 4909beee32 fix #45 2024-11-02 18:49:16 +08:00
Tao Chen d85a799d0b set user_id=0 at peers, when the user is deleted 2024-11-02 08:24:07 +08:00
Tao Chen d38117107d When login, peer doesn't exist, it should create 2024-11-02 08:19:44 +08:00
Tao Chen 6a7ef29089 delete the token when delete a peer 2024-11-02 08:02:03 +08:00
Tao Chen a4dd39043e add MyPeers for user 2024-11-02 07:35:26 +08:00
Tao Chen dfcc7d54c1 add email for register 2024-11-02 05:43:55 +08:00
Tao Chen 71d1c431a9 add DeviceId to userToken 2024-11-02 05:42:47 +08:00
Tao Chen d31d669734 logout should unbind uuid and uid of peer 2024-11-02 05:07:41 +08:00
Tao Chen c021ebfbdf fix bug ValidateOauthProvider location 2024-11-02 04:20:00 +08:00
Tao Chen 485ae54e9e re-construct oauth 2024-11-02 04:01:28 +08:00
ljw c5e3482538 load key from file 2024-11-01 14:56:11 +08:00
c4b45a51d5 Merge pull request #42 from IamTaoChen/docker
Docker Optimize
2024-11-01 13:10:59 +08:00
Tao Chen 51186b66dc add more 2024-11-01 01:33:08 +08:00
Tao Chen 2627f6fb06 optimize scripts 2024-11-01 01:33:05 +08:00
Tao Chen a6a6c3aefe optimize build speed, like cache and mirror 2024-11-01 01:32:45 +08:00
Tao Chen d21b5809c4 add ARG CONTRY=CN to improve the alpinelinux install speed 2024-11-01 00:29:33 +08:00
Tao Chen 503e908eb6 add bash to run dev-docker 2024-11-01 00:28:43 +08:00
ljw 6c94566e83 add remove user token #34 2024-10-31 22:29:12 +08:00
095640af3f Merge pull request #40 from IamTaoChen/resetEmptyPassWD
Reset empty password
2024-10-31 18:46:34 +08:00
Tao Chen 5488babfb3 Merge branch 'master' into resetEmptyPassWD 2024-10-31 16:35:32 +08:00
Tao Chen 962e07d65f ommit check old passwd if password is empty 2024-10-31 16:23:06 +08:00
Tao Chen bd2eeae01a add IsPasswordEmpty... 2024-10-31 16:22:42 +08:00
ljw efead3f9c7 up readme #28 2024-10-31 15:48:36 +08:00
ljw 7cb838693a up readme #28 2024-10-31 15:47:39 +08:00
ljw e2fda47cbb add register 2024-10-31 15:14:30 +08:00
ljw 0ed40318cb up oauth 2024-10-31 14:03:48 +08:00
122b3baf6f Merge pull request #36 from IamTaoChen/oidc-for-web
OIDC for web
2024-10-31 11:10:46 +08:00
Tao Chen 06f007ebd9 rename build stage 2024-10-31 09:21:43 +08:00
Tao Chen 7c1fc4fa6d add some /admin/ to surport web OIDC 2024-10-31 09:21:30 +08:00
ljw e1424bcea7 fix buidconfirm 2024-10-30 20:59:51 +08:00
ljw b3edc9d112 up del user 2024-10-30 19:34:56 +08:00
68065d3372 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 f6a0c6466f modify delete user 2024-10-30 16:33:01 +08:00
Tao Chen 1070022277 update README 2024-10-30 16:31:47 +08:00
Tao Chen f9a8a966e5 update README for OIDC 2024-10-30 16:29:49 +08:00
Tao Chen 3041fb82d9 delete user from user_thirds, too 2024-10-30 15:59:33 +08:00
ljw 0ddfbdbd23 up v 2024-10-30 15:46:12 +08:00
ljw eada376783 add docker-compose-dev.yaml 2024-10-30 15:34:45 +08:00
bc92ffc106 Merge pull request #30 from IamTaoChen/oidc
Add General OIDC Login
2024-10-30 14:40:10 +08:00
Tao Chen 60b7ccdb3a bind oidc ThirdEmail 2024-10-29 23:09:54 +08:00
Tao Chen 9eff8ea33a fix: spelling 2024-10-29 23:00:17 +08:00
Tao Chen 17305b6a6f fix bug 2024-10-29 18:48:37 +08:00
Tao Chen 7d83226655 fix bug - oidc scopes 2024-10-29 18:46:45 +08:00
ljw 001884a4d8 fix oauth register #26 #23 2024-10-29 15:31:27 +08:00
Tao Chen 461b100b6c fix bug 2024-10-29 14:27:15 +08:00
Tao Chen 4105f14a3f try add oidc 2024-10-29 11:51:01 +08:00
Tao Chen 4baa8d392e optimize docker 2024-10-29 11:50:55 +08:00
Tao Chen bf48e8ba19 optimize build.sh 2024-10-29 10:58:17 +08:00
ljw 991fc86fa6 add last online ip #24 2024-10-28 20:24:34 +08:00
ljw 1191ade8af up address book add version #20 2024-10-28 19:48:47 +08:00
ljw 02232fc343 up peer update 2024-10-28 19:15:13 +08:00
ljw 497dfb2402 fix bug #27 2024-10-28 16:08:33 +08:00
ljw f21fb5d437 up 2024-10-28 14:54:00 +08:00
ljw 8ee93b530d add address book name &
add share address book
2024-10-28 14:51:07 +08:00
ljw e833c0e0c0 up readme 2024-10-23 11:09:28 +08:00
ljw d5cf3544f1 fix group 2024-10-23 11:09:13 +08:00
ljw ca9a3155af up build.yml 2024-10-23 09:19:02 +08:00
ljw b1817ca63f add armv7l build #21 2024-10-23 09:03:01 +08:00
ljw 476f251d2c add ru lang 2024-10-22 19:46:38 +08:00
ljw e2fa928648 add ko lang,but validator dont have translations ko 2024-10-22 12:21:32 +08:00
ljw 3f9b370dd6 Merge branch 'master' of https://github.com/lejianwen/rustdesk-api 2024-10-22 11:31:01 +08:00
7cf4fb4157 Merge pull request #19 from jkh0kr/master
Add Korean language file
2024-10-22 11:30:49 +08:00
ljw 6fb1235dd8 up 2024-10-22 11:28:27 +08:00
진기환 8aa779f611 Add Korean language file 2024-10-22 10:35:58 +09:00
ljw 5c4b6b5cd0 up readme 2024-10-21 21:35:27 +08:00
ljw 47ab3958c9 add file conn log 2024-10-21 21:08:25 +08:00
ljw cbb3764b60 up build.yml 2024-10-20 20:26:08 +08:00
ljw f5aadec6c0 fix group create type 2024-10-20 20:15:12 +08:00
ljw de0523852a up readme 2024-10-20 19:40:49 +08:00
ba85790d8c Merge pull request #17 from Ogannesson/master
Add proxy option for Google Oauthon
2024-10-20 19:05:01 +08:00
Oganneson 0091a9dd7f 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 e142cc00c6 add conn log 2024-10-18 15:05:58 +08:00
ljw 488fedf784 fix write when heartbeat #14 2024-10-17 10:24:02 +08:00
ljw ceb65cd391 fix write when heartbeat #14 2024-10-16 21:32:55 +08:00
ljw 72c196c6f9 fix pc add #13 2024-10-16 10:10:20 +08:00
ljw 28f047ec79 fix pc add #13 2024-10-16 09:28:39 +08:00
ljw 7e0cc33c91 Revert "add webclient v2 preview"
This reverts commit e269341d51.
2024-10-15 16:35:34 +08:00
ljw 05a2961814 Revert "up readme"
This reverts commit dc8af3a460.
2024-10-15 16:35:34 +08:00
ljw dc8af3a460 up readme 2024-10-15 16:13:42 +08:00
ljw e269341d51 add webclient v2 preview 2024-10-15 16:11:40 +08:00
ljw ebe5bc4c3a fix docs 2024-10-15 14:51:19 +08:00
ljw b4965e8885 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 0eb733cc33 up readme 2024-10-12 10:02:04 +08:00
ljw 1e41784bb9 Revert "Revert "up readme""
This reverts commit 3d4e068266.
2024-10-11 22:45:35 +08:00
ljw 3d4e068266 Revert "up readme"
This reverts commit abc0e9d907.
2024-10-11 22:06:20 +08:00
ljw abc0e9d907 up readme 2024-10-11 21:39:58 +08:00
ljw 5d361d05cc up readme 2024-10-11 10:11:42 +08:00
ljw 823c052d6d up readme 2024-10-10 13:00:29 +08:00
ljw ebe39af682 fix readme 2024-10-10 12:40:54 +08:00
ljw 8b266d4fee up gorm logger & add share to guest by web client 2024-10-09 15:53:08 +08:00
ljw 2ab61512cf build tag 2024-09-29 12:47:04 +08:00
ljw ee679d09f7 build default push to docker 2024-09-29 12:39:33 +08:00
ljw 71c44a501b fix build 2024-09-29 12:23:34 +08:00
f8a672070f Merge pull request #5 from gigaion/new-build-1
build.yml - Add GHCR & Dynamic Inputs
2024-09-29 12:12:17 +08:00
ljw 609dfaeea0 upgrade: init by i18n
add: batch delete peer
add: batch peer to addressbook
2024-09-29 11:53:58 +08:00
Gigaion 71e3529c2f build.yml - Add GHCR & Dynamic Inputs
build.yml - Add GHCR & Dynamic Inputs
2024-09-28 13:30:44 -07:00
ljw 61fc53858b test 2024-09-28 11:01:23 +08:00
ljw a843748f60 test 2024-09-28 10:12:56 +08:00
ljw c266f57a46 test 2024-09-28 10:05:04 +08:00
ljw 86f22e2666 test 2024-09-28 09:57:32 +08:00
ljw c42e865784 fix build docker image 2024-09-28 09:39:26 +08:00
ljw 5ad31679a8 fix build docker image 2024-09-28 09:33:59 +08:00
ljw 56c84c854a up build 2024-09-27 22:08:36 +08:00
ljw 8118107f0f up build 2024-09-27 22:06:04 +08:00
ljw d3aebec722 up build 2024-09-27 22:03:06 +08:00
ljw 808aa47856 up build 2024-09-27 21:48:48 +08:00
ljw 90f8c6b4e6 up build 2024-09-27 21:37:12 +08:00
ljw 5de0b522f1 up build 2024-09-27 21:29:19 +08:00
ljw 98e9828e1f up build docker echo manifest 2024-09-27 21:20:54 +08:00
ljw 71ff7ebd42 up build docker 2024-09-27 20:10:17 +08:00
ljw bd084ced52 up build docker 2024-09-27 20:06:49 +08:00
ljw cab04765f0 up build docker 2024-09-27 19:40:48 +08:00
ljw 5deaaa8a00 up build docker 2024-09-27 18:49:22 +08:00
ljw 58d39c7aa3 up build docker 2024-09-27 18:48:09 +08:00
ljw 87bec98a7f up build docker 2024-09-27 17:49:17 +08:00
ljw 74662ae58b up build docker 2024-09-27 17:32:25 +08:00
ljw 814bf48a54 up build docker 2024-09-27 17:26:40 +08:00
ljw a1dba6c564 up build docker 2024-09-27 17:25:30 +08:00
ljw 418b182641 up build docker 2024-09-27 17:24:25 +08:00
ljw bcbbf1099d up build docker 2024-09-27 17:15:06 +08:00
ljw 85c8377240 up build docker 2024-09-27 16:52:02 +08:00
ljw 264ae2dffa up build docker 2024-09-27 16:07:06 +08:00
ljw b069e7876d up build docker 2024-09-27 15:59:12 +08:00
ljw 41670b0d00 up build docker 2024-09-27 15:31:43 +08:00
ljw 5bf8e176aa up build docker 2024-09-27 15:23:19 +08:00
ljw 9787ca0895 up build docker 2024-09-27 15:18:45 +08:00
ljw 04098cb6e1 up build docker 2024-09-27 14:58:17 +08:00
ljw 6411be66ba up build docker 2024-09-27 14:54:11 +08:00
ljw 0d3fd56c5e up build docker 2024-09-27 14:46:39 +08:00
ljw 9e7908c035 up build docker 2024-09-27 14:35:43 +08:00
ljw 08c7647c10 up build docker 2024-09-27 14:29:55 +08:00
ljw cbbc8259e8 up build docker 2024-09-27 14:24:17 +08:00
ljw aff735335b up build docker 2024-09-27 14:19:56 +08:00
ljw 47f1043cc2 up build docker 2024-09-27 14:18:36 +08:00
ljw 871bb328f0 up build docker 2024-09-27 14:16:59 +08:00
ljw d18d8c0e3f up build docker 2024-09-27 14:10:50 +08:00
ljw db7c43353d up release_arm64.yml 2024-09-27 10:43:48 +08:00
ljw 1847697ceb up release_arm64.yml 2024-09-27 10:38:14 +08:00
ljw f5ffc433f7 up docker_arm64.yml 2024-09-26 14:36:15 +08:00
ljw fe36f7b2f0 up docker_arm64.yml 2024-09-26 14:33:01 +08:00
ljw 4f2216d023 up docker_arm64.yml 2024-09-26 14:26:15 +08:00
ljw a214113de5 up docker_arm64.yml 2024-09-26 14:24:14 +08:00
ljw ea9dc84b91 add docker_arm64.yml 2024-09-26 14:18:57 +08:00
ljw cf6bd41319 up README 2024-09-26 13:38:43 +08:00
ljw 0ab1ff9e18 up README_EN.md 2024-09-26 13:21:17 +08:00
ljw c7b0996443 test release_arm64.yml 2024-09-26 11:49:11 +08:00
ljw 62ebc66259 test release_arm64.yml 2024-09-26 11:46:17 +08:00
ljw 6ce6a95e07 fix Dockerfile 2024-09-26 09:29:02 +08:00
ljw 467b61fb20 add i18n 2024-09-25 22:42:36 +08:00
ljw c99261c12d add i18n 2024-09-25 22:41:57 +08:00
ljw c9d70584cc up readme 2024-09-24 21:16:03 +08:00
ljw 0a81b5b5f8 fix group peers 2024-09-24 19:35:20 +08:00
ljw 95a1bd6c21 up readme 2024-09-24 15:22:21 +08:00
ljw 8c2f6f0929 up docs 2024-09-24 15:16:07 +08:00
ljw c5b43fae08 up readme 2024-09-24 15:15:26 +08:00
ljw c485035e74 add personal apis 2024-09-24 14:43:27 +08:00
ljw a161e3ba10 up web client can get pwd if exist 2024-09-24 10:15:04 +08:00
ljw 5b5943b287 add docker.yml 2024-09-23 14:43:43 +08:00
ljw 69ad1b2e80 up README.md 2024-09-23 10:21:43 +08:00
ljw 01aad9bb15 up release.yml 2024-09-23 10:13:44 +08:00
ljw b4b246def5 up release.yml 2024-09-23 10:10:29 +08:00
ljw 752d376dd7 up test.yml 2024-09-23 10:07:10 +08:00
ljw 601e1de3b3 up test.yml 2024-09-22 21:40:17 +08:00
ljw 758f1fb1c9 up test.yml 2024-09-22 21:28:43 +08:00
ljw d394931fce up test.yml 2024-09-22 20:30:12 +08:00
ljw 192b3b11a9 up test.yml 2024-09-22 19:46:37 +08:00
ljw 4ec08cf03a up test.yml 2024-09-22 17:58:42 +08:00
cb1a06e4a9 Update test.yml 2024-09-21 22:49:06 +08:00
67b4678b2d Update test.yml 2024-09-21 21:55:36 +08:00
8d390dae50 Update test.yml 2024-09-21 21:44:43 +08:00
a4b40d746b Update go.yml 2024-09-21 21:43:31 +08:00
ddfd9f7923 Update test.yml 2024-09-21 21:40:36 +08:00
521e2c72f5 Create test.yml 2024-09-21 21:35:55 +08:00
79aa573e0f Update go.yml 2024-09-21 21:17:03 +08:00
5b6e56a8ec Create go.yml 2024-09-21 21:13:54 +08:00
227 changed files with 4965 additions and 219154 deletions
+61
View File
@@ -0,0 +1,61 @@
name: Build & Push Docker Image
# Builds the Docker image and publishes it to the Gitea container registry
# at git.hackner.dev/thomas/rustdesk-api.
#
# Requires a running Gitea Actions runner (act_runner) and two repo secrets
# (Settings -> Actions -> Secrets):
# REGISTRY_USER - Gitea username (e.g. thomas)
# REGISTRY_PASSWORD - a Gitea access token with "write:package" scope
#
# (Alternatively the built-in ${{ secrets.GITHUB_TOKEN }} can be used with
# username ${{ gitea.actor }} if your Gitea grants it package write access.)
on:
push:
branches:
- master
tags:
- 'v*.*.*'
workflow_dispatch:
env:
REGISTRY: git.hackner.dev
IMAGE_NAME: ${{ github.repository }} # -> thomas/rustdesk-api
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to the Gitea container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.dev
push: true
build-args: |
GO_VERSION=1.23
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
-443
View File
@@ -1,443 +0,0 @@
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.22' # 选择 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.cc/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.cc/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
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@master
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@master
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@master
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@master
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@master
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@master
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
View File
@@ -1,337 +0,0 @@
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.22' # 选择 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.cc/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.cc/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@master
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@master
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
+1 -1
View File
@@ -5,4 +5,4 @@ runtime/*
go.sum go.sum
resources/admin resources/admin
release release
data data/rustdeskapi.db
+3 -4
View File
@@ -42,11 +42,11 @@ RUN if [ "$COUNTRY" = "CN" ] ; then \
fi && \ fi && \
apk update && apk add --no-cache git apk update && apk add --no-cache git
ARG FREONTEND_GIT_REPO=https://github.com/lejianwen/rustdesk-api-web.git ARG FRONTEND_GIT_REPO=https://github.com/lejianwen/rustdesk-api-web.git
ARG FRONTEND_GIT_BRANCH=master ARG FRONTEND_GIT_BRANCH=master
# Clone the frontend repository # Clone the frontend repository
RUN git clone -b $FRONTEND_GIT_BRANCH $FREONTEND_GIT_REPO . RUN git clone -b $FRONTEND_GIT_BRANCH $FRONTEND_GIT_REPO .
# Install required tools without caching index to minimize image size # Install required tools without caching index to minimize image size
RUN if [ "$COUNTRY" = "CN" ] ; then \ RUN if [ "$COUNTRY" = "CN" ] ; then \
@@ -76,7 +76,6 @@ COPY --from=builder-backend /app/release /app/
COPY --from=builder-backend /app/conf /app/conf/ COPY --from=builder-backend /app/conf /app/conf/
COPY --from=builder-backend /app/resources /app/resources/ COPY --from=builder-backend /app/resources /app/resources/
COPY --from=builder-backend /app/docs /app/docs/ COPY --from=builder-backend /app/docs /app/docs/
COPY --from=builder-backend /app/http/templates /app/http/templates
# Copy frontend build from builder2 stage # Copy frontend build from builder2 stage
COPY --from=builder-admin-frontend /frontend/dist/ /app/resources/admin/ COPY --from=builder-admin-frontend /frontend/dist/ /app/resources/admin/
@@ -92,4 +91,4 @@ VOLUME /app/data
EXPOSE 21114 EXPOSE 21114
# Define the command to run the application # Define the command to run the application
CMD ["./apimain"] CMD ["./apimain"]
+192 -173
View File
@@ -1,8 +1,9 @@
# RustDesk API # RustDesk API
[English Doc](README_EN.md) [中文文档](README_EN.md)
本项目使用 Go 实现了 RustDesk API,并包含了 Web Admin 和 Web 客户端。RustDesk 是一个远程桌面软件,提供了自托管的解决方案。 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> <div align=center>
<img src="https://img.shields.io/badge/golang-1.22-blue"/> <img src="https://img.shields.io/badge/golang-1.22-blue"/>
@@ -13,229 +14,237 @@
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/> <img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/>
</div> </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.
- PC端API
- 个人版API # Features
- 登录
- 地址簿 - PC API
- 群组 - Personal API
- 授权登录 - Login
- 支持`github`, `google``OIDC` 登录, - Address Book
- 支持`web后台`授权登录 - Groups
- 支持`LDAP`(AD和OpenLDAP已测试), 如果API Server配置了LDAP - Authorized login,
- supports `GitHub`, `Google` and `OIDC` login,
- supports `web admin` authorized login,
- supports LDAP(test AD and openladp) if API Server config
- i18n - i18n
- Web Admin - Web Admin
- 用户管理 - User Management
- 设备管理 - Device Management
- 地址簿管理 - Address Book Management
- 标签管理 - Tag Management
- 群组管理 - Group Management
- Oauth 管理 - OAuth Management
- 配置LDAP, 配置文件或者环境变量 - LDAP Config by config file or ENV
- 登录日志 - Login Logs
- 链接日志 - Connection Logs
- 文件传输日志 - File Transfer Logs
- 快速使用web client - Quick access to web client
- i18n - i18n
- 通过 web client 分享给游客 - Share to guest by web client
- server控制(一些官方的简单的指令 [WIKI](https://github.com/lejianwen/rustdesk-api/wiki/Rustdesk-Command)) - Server control (some simple official commands [WIKI](https://github.com/lejianwen/rustdesk-api/wiki/Rustdesk-Command))
- Web Client - Web Client
- 自动获取API server - Automatically obtain API server
- 自动获取ID服务器和KEY - Automatically obtain ID server and KEY
- 自动获取地址簿 - Automatically obtain address book
- 游客通过临时分享链接直接远程到设备 - Visitors are remotely to the device via a temporary sharing link
- v2 Preview
- CLI - CLI
- 重置管理员密码 - Reset admin password
## 功能 ## Overview
### API Service
### API 服务 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.
基本实现了PC端基础的接口。支持Personal版本接口,可以通过配置文件`rustdesk.personal`或环境变量`RUSTDESK_API_RUSTDESK_PERSONAL`来控制是否启用
<table> <table>
<tr> <tr>
<td width="50%" align="center" colspan="2"><b>登录</b></td> <td width="50%" align="center" colspan="2"><b>Login</b></td>
</tr> </tr>
<tr> <tr>
<td width="50%" align="center" colspan="2"><img src="docs/pc_login.png"></td> <td width="50%" align="center" colspan="2"><img src="docs/en_img/pc_login.png"></td>
</tr> </tr>
<tr> <tr>
<td width="50%" align="center"><b>地址簿</b></td> <td width="50%" align="center"><b>Address Book</b></td>
<td width="50%" align="center"><b>群组</b></td> <td width="50%" align="center"><b>Groups</b></td>
</tr> </tr>
<tr> <tr>
<td width="50%" align="center"><img src="docs/pc_ab.png"></td> <td width="50%" align="center"><img src="docs/en_img/pc_ab.png"></td>
<td width="50%" align="center"><img src="docs/pc_gr.png"></td> <td width="50%" align="center"><img src="docs/en_img/pc_gr.png"></td>
</tr> </tr>
</table> </table>
### Web Admin: ### Web Admin
* 使用前后端分离,提供用户友好的管理界面,主要用来管理和展示。前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web) * 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)
* 后台访问地址是`http://<your server>[:port]/_admin/` * Admin panel URL: `http://<your server[:port]>/_admin/`
* 初次安装管理员为用户名为`admin`,密码将在控制台打印,可以通过[命令行](#CLI)更改密码 * 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) ![img.png](./docs/init_admin_pwd.png)
1. 管理员界面
![web_admin](docs/web_admin.png)
2. 普通用户界面
![web_user](docs/web_admin_user.png)
3. 每个用户可以多个地址簿,也可以将地址簿共享给其他用户 1. Admin interface:
4. 分组可以自定义,方便管理,暂时支持两种类型: `共享组``普通组` ![web_admin](docs/en_img/web_admin.png)
5. 可以直接打开webclient,方便使用;也可以分享给游客,游客可以直接通过webclient远程到设备 2. Regular user interface:
6. Oauth,支持了`Github`, `Google` 以及 `OIDC`, 需要创建一个`OAuth App`,然后配置到后台 ![web_user](docs/en_img/web_admin_user.png)
- 对于`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/oauth/callback`
,比如`http://127.0.0.1:21114/api/oauth/callback`
7. 登录日志
8. 链接日志
9. 文件传输日志
10. server控制
- `简易模式`,已经界面化了一些简单的指令,可以直接在后台执行 3. Each user can have multiple address books, which can also be shared with other users.
![rustdesk_command_simple](./docs/rustdesk_command_simple.png) 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 支持**, 当在API Server上设置了LDAP(已测试AD和LDAP),可以通过LDAP中的用户信息进行登录 https://github.com/lejianwen/rustdesk-api/issues/114 ,如果LDAP验证失败,返回本地用户
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: ### Web Client:
1. 如果已经登录了后台,web client将自动直接登录 1. If you're already logged into the admin panel, the web client will log in automatically.
2. 如果没登录后台,点击右上角登录即可,api server已经自动配置好了 2. If you're not logged in, simply click the login button in the top right corner, and the API server will be
3. 登录后,会自动同步ID服务器和KEY pre-configured.
4. 登录后,会将地址簿自动保存到web client中,方便使用 3. After logging in, the ID server and key will be automatically synced.
5. 现已支持`v2 Preview`,访问路径是`/webclient2` 4. The address book will also be automatically saved to the web client for convenient use.
![webclientv2](./docs/webclientv2.png)
6. `v2 preview` 部署,参考[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.
### 自动化文档: 使用 Swag 生成 API 文档,方便开发者理解和使用 API。 1. Admin panel docs: `<your server[:port]>/admin/swagger/index.html`
2. PC client docs: `<your server[:port]>/swagger/index.html`
1. 后台文档 `<youer server[:port]>/admin/swagger/index.html`
2. PC端文档 `<youer server[:port]>/swagger/index.html`
![api_swag](docs/api_swag.png) ![api_swag](docs/api_swag.png)
### CLI ### CLI
```bash ```bash
# 查看帮助 # help
./apimain -h ./apimain -h
``` ```
#### 重置管理员密码 #### Reset admin password
```bash ```bash
./apimain reset-admin-pwd <pwd> ./apimain reset-admin-pwd <pwd>
``` ```
## 安装与运行 ## Installation and Setup
### 相关配置 ### Configuration
* [配置文件](./conf/config.yaml) * [Config File](./conf/config.yaml)
* 参考`conf/config.yaml`配置文件,修改相关配置。 * Modify the configuration in `conf/config.yaml`.
* 如果`gorm.type``sqlite`,则不需要配置mysql相关配置。 * If `gorm.type` is set to `sqlite`, MySQL-related configurations are not required.
* 语言如果不设置默认为`zh-CN` * Language support: `en` and `zh-CN` are supported. The default is `zh-CN`.
### 环境变量
环境变量和配置文件`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` |
| -----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配置----- | ---------- | ---------- |
| 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` |
### 运行 ### 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`.
#### docker运行 | 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` |
1. 直接docker运行,配置可以通过挂载配置文件`/app/conf/config.yaml`来修改,或者通过环境变量覆盖配置文件中的配置 ### 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 ```bash
docker run -d --name rustdesk-api -p 21114:21114 \ docker run -d --name rustdesk-api -p 21114:21114 \
-v /data/rustdesk/api:/app/data \ -v /data/rustdesk/api:/app/data \
-e TZ=Asia/Shanghai \ -e RUSTDESK_API_LANG=en \
-e RUSTDESK_API_LANG=zh-CN \
-e RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 \ -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_RELAY_SERVER=192.168.1.66:21117 \
-e RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114 \ -e RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114 \
-e RUSTDESK_API_RUSTDESK_KEY=<key> \ -e RUSTDESK_API_RUSTDESK_KEY=abc123456 \
lejianwen/rustdesk-api lejianwen/rustdesk-api
``` ```
2. 使用`docker compose`,参考[WIKI](https://github.com/lejianwen/rustdesk-api/wiki) 2. Using `docker-compose`,look [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
#### 下载release直接运行 #### Running from Release
[下载地址](https://github.com/lejianwen/rustdesk-api/releases) Download the release from [release](https://github.com/lejianwen/rustdesk-api/releases).
#### 源码安装 #### Source Installation
1. 克隆仓库 1. Clone the repository:
```bash ```bash
git clone https://github.com/lejianwen/rustdesk-api.git git clone https://github.com/lejianwen/rustdesk-api.git
cd rustdesk-api cd rustdesk-api
``` ```
2. 安装依赖 2. Install dependencies:
```bash ```bash
go mod tidy go mod tidy
#安装swag,如果不需要生成文档,可以不安装 # Install Swag if you need to generate documentation; otherwise, you can skip this step
go install github.com/swaggo/swag/cmd/swag@latest go install github.com/swaggo/swag/cmd/swag@latest
``` ```
3. 编译后台前端,前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)中 3. Build the admin front-end (the front-end code is
in [rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)):
```bash ```bash
cd resources cd resources
mkdir -p admin mkdir -p admin
@@ -245,23 +254,33 @@
npm run build npm run build
cp -ar dist/* ../admin/ cp -ar dist/* ../admin/
``` ```
4. 运行
4. Run:
```bash ```bash
#直接运行 # Run directly
go run cmd/apimain.go go run cmd/apimain.go
#或者使用generate_api.go生成api并运行 # Or generate and run the API using generate_api.go
go generate generate_api.go go generate generate_api.go
``` ```
5. 编译,如果想自己编译,先cd到项目根目录,然后windows下直接运行`build.bat`,linux下运行`build.sh`,编译后会在`release` > **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
> ```
6. 打开浏览器访问`http://<your server[:port]>/_admin/`,默认用户名密码为`admin`,请及时更改密码。 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.
#### 使用`lejianwen/server-s6`镜像运行 #### 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 - github https://github.com/lejianwen/rustdesk-server
```yaml ```yaml
@@ -291,28 +310,28 @@
- RUSTDESK_API_JWT_KEY=xxxxxx # jwt key - RUSTDESK_API_JWT_KEY=xxxxxx # jwt key
volumes: volumes:
- /data/rustdesk/server:/data - /data/rustdesk/server:/data
- /data/rustdesk/api:/app/data #将数据库挂载 - /data/rustdesk/api:/app/data # mount the database
networks: networks:
- rustdesk-net - rustdesk-net
restart: unless-stopped restart: unless-stopped
``` ```
## Others
## 其他
- [WIKI](https://github.com/lejianwen/rustdesk-api/wiki) - [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
- [链接超时问题](https://github.com/lejianwen/rustdesk-api/issues/92) - [Connection Timeout](https://github.com/lejianwen/rustdesk-api/issues/92)
- [修改客户端ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer) - [Change client ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)
- [webclient来源](https://hub.docker.com/r/keyurbhole/flutter_web_desk) - [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"> <a href="https://github.com/lejianwen/rustdesk-api/graphs/contributors">
<img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" /> <img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" />
</a> </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
+187 -169
View File
@@ -1,7 +1,9 @@
# RustDesk API # RustDesk API
This project implements the RustDesk API using Go, and includes both a web UI and web client. RustDesk is a remote [English Doc](README.md)
desktop software that provides self-hosted solutions.
本项目使用 Go 实现了 RustDesk 的 API,并包含了 Web Admin 和 Web 客户端。
<div align=center> <div align=center>
<img src="https://img.shields.io/badge/golang-1.22-blue"/> <img src="https://img.shields.io/badge/golang-1.22-blue"/>
@@ -12,229 +14,237 @@ desktop software that provides self-hosted solutions.
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/> <img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/>
</div> </div>
# Features ## 搭配[lejianwen/rustdesk-server]使用更佳。
> [lejianwen/rustdesk-server]fork自RustDesk Server官方仓库
> 1. 解决了使用API链接超时问题
> 2. 可以强制登录后才能发起链接
> 3. 支持客户端websocket
- PC API
- Personal API
- Login # 特性
- Address Book
- Groups - PC端API
- Authorized login, - 个人版API
- supports `GitHub`, `Google` and `OIDC` login, - 登录
- supports `web admin` authorized login, - 地址簿
- supports LDAP(test AD and openladp) if API Server config - 群组
- 授权登录
- 支持`github`, `google``OIDC` 登录,
- 支持`web后台`授权登录
- 支持`LDAP`(AD和OpenLDAP已测试), 如果API Server配置了LDAP
- i18n - i18n
- Web Admin - Web Admin
- User Management - 用户管理
- Device Management - 设备管理
- Address Book Management - 地址簿管理
- Tag Management - 标签管理
- Group Management - 群组管理
- OAuth Management - Oauth 管理
- LDAP Config by config file or ENV - 配置LDAP, 配置文件或者环境变量
- Login Logs - 登录日志
- Connection Logs - 链接日志
- File Transfer Logs - 文件传输日志
- Quick access to web client - 快速使用web client
- i18n - i18n
- Share to guest by web client - 通过 web client 分享给游客
- Server control (some simple official commands [WIKI](https://github.com/lejianwen/rustdesk-api/wiki/Rustdesk-Command)) - server控制(一些官方的简单的指令 [WIKI](https://github.com/lejianwen/rustdesk-api/wiki/Rustdesk-Command))
- Web Client - Web Client
- Automatically obtain API server - 自动获取API server
- Automatically obtain ID server and KEY - 自动获取ID服务器和KEY
- Automatically obtain address book - 自动获取地址簿
- Visitors are remotely to the device via a temporary sharing link - 游客通过临时分享链接直接远程到设备
- CLI - 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. ### API 服务
基本实现了PC端基础的接口。支持Personal版本接口,可以通过配置文件`rustdesk.personal`或环境变量`RUSTDESK_API_RUSTDESK_PERSONAL`来控制是否启用
<table> <table>
<tr> <tr>
<td width="50%" align="center" colspan="2"><b>Login</b></td> <td width="50%" align="center" colspan="2"><b>登录</b></td>
</tr> </tr>
<tr> <tr>
<td width="50%" align="center" colspan="2"><img src="docs/en_img/pc_login.png"></td> <td width="50%" align="center" colspan="2"><img src="docs/pc_login.png"></td>
</tr> </tr>
<tr> <tr>
<td width="50%" align="center"><b>Address Book</b></td> <td width="50%" align="center"><b>地址簿</b></td>
<td width="50%" align="center"><b>Groups</b></td> <td width="50%" align="center"><b>群组</b></td>
</tr> </tr>
<tr> <tr>
<td width="50%" align="center"><img src="docs/en_img/pc_ab.png"></td> <td width="50%" align="center"><img src="docs/pc_ab.png"></td>
<td width="50%" align="center"><img src="docs/en_img/pc_gr.png"></td> <td width="50%" align="center"><img src="docs/pc_gr.png"></td>
</tr> </tr>
</table> </table>
### Web Admin ### Web Admin:
* The frontend and backend are separated to provide a user-friendly management interface, primarily for managing and * 使用前后端分离,提供用户友好的管理界面,主要用来管理和展示。前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)
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/` * 后台访问地址是`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). * 初次安装管理员为用户名为`admin`,密码将在控制台打印,可以通过[命令行](#CLI)更改密码
![img.png](./docs/init_admin_pwd.png) ![img.png](./docs/init_admin_pwd.png)
1. 管理员界面
![web_admin](docs/web_admin.png)
2. 普通用户界面
![web_user](docs/web_admin_user.png)
1. Admin interface: 3. 每个用户可以多个地址簿,也可以将地址簿共享给其他用户
![web_admin](docs/en_img/web_admin.png) 4. 分组可以自定义,方便管理,暂时支持两种类型: `共享组``普通组`
2. Regular user interface: 5. 可以直接打开webclient,方便使用;也可以分享给游客,游客可以直接通过webclient远程到设备
![web_user](docs/en_img/web_admin_user.png) 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控制
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`. ![rustdesk_command_simple](./docs/rustdesk_command_simple.png)
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/oauth/callback`,
e.g., `http://127.0.0.1:21114/api/oauth/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 支持**, 当在API Server上设置了LDAP(已测试AD和LDAP),可以通过LDAP中的用户信息进行登录 https://github.com/lejianwen/rustdesk-api/issues/114 ,如果LDAP验证失败,返回本地用户
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: ### Web Client:
1. If you're already logged into the admin panel, the web client will log in automatically. 1. 如果已经登录了后台,web client将自动直接登录
2. If you're not logged in, simply click the login button in the top right corner, and the API server will be 2. 如果没登录后台,点击右上角登录即可,api server已经自动配置好了
pre-configured. 3. 登录后,会自动同步ID服务器和KEY
3. After logging in, the ID server and key will be automatically synced. 4. 登录后,会将地址簿自动保存到web client中,方便使用
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` ### 自动化文档: 使用 Swag 生成 API 文档,方便开发者理解和使用 API。
2. PC client docs: `<your server[:port]>/swagger/index.html`
1. 后台文档 `<youer server[:port]>/admin/swagger/index.html`
2. PC端文档 `<youer server[:port]>/swagger/index.html`
![api_swag](docs/api_swag.png) ![api_swag](docs/api_swag.png)
### CLI ### CLI
```bash ```bash
# help # 查看帮助
./apimain -h ./apimain -h
``` ```
#### Reset admin password #### 重置管理员密码
```bash ```bash
./apimain reset-admin-pwd <pwd> ./apimain reset-admin-pwd <pwd>
``` ```
## Installation and Setup ## 安装与运行
### Configuration ### 相关配置
* [Config File](./conf/config.yaml) * [配置文件](./conf/config.yaml)
* Modify the configuration in `conf/config.yaml`. * 参考`conf/config.yaml`配置文件,修改相关配置。
* If `gorm.type` is set to `sqlite`, MySQL-related configurations are not required. * 如果`gorm.type``sqlite`,则不需要配置mysql相关配置。
* Language support: `en` and `zh-CN` are supported. The default is `zh-CN`. * 语言如果不设置默认为`zh-CN`
### 环境变量
环境变量和配置文件`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` |
### 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 | #### docker运行
|--------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------|
| 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` |
| ----- 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 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 1. 直接docker运行,配置可以通过挂载配置文件`/app/conf/config.yaml`来修改,或者通过环境变量覆盖配置文件中的配置
#### 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 ```bash
docker run -d --name rustdesk-api -p 21114:21114 \ docker run -d --name rustdesk-api -p 21114:21114 \
-v /data/rustdesk/api:/app/data \ -v /data/rustdesk/api:/app/data \
-e RUSTDESK_API_LANG=en \ -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_ID_SERVER=192.168.1.66:21116 \
-e RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 \ -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_API_SERVER=http://192.168.1.66:21114 \
-e RUSTDESK_API_RUSTDESK_KEY=abc123456 \ -e RUSTDESK_API_RUSTDESK_KEY=<key> \
lejianwen/rustdesk-api lejianwen/rustdesk-api
``` ```
2. Using `docker-compose`,look [WIKI](https://github.com/lejianwen/rustdesk-api/wiki) 2. 使用`docker compose`,参考[WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
#### Running from Release #### 下载release直接运行
Download the release from [release](https://github.com/lejianwen/rustdesk-api/releases). [下载地址](https://github.com/lejianwen/rustdesk-api/releases)
#### Source Installation #### 源码安装
1. Clone the repository: 1. 克隆仓库
```bash ```bash
git clone https://github.com/lejianwen/rustdesk-api.git git clone https://github.com/lejianwen/rustdesk-api.git
cd rustdesk-api cd rustdesk-api
``` ```
2. Install dependencies: 2. 安装依赖
```bash ```bash
go mod tidy go mod tidy
# Install Swag if you need to generate documentation; otherwise, you can skip this step #安装swag,如果不需要生成文档,可以不安装
go install github.com/swaggo/swag/cmd/swag@latest go install github.com/swaggo/swag/cmd/swag@latest
``` ```
3. Build the admin front-end (the front-end code is 3. 编译后台前端,前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)中
in [rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)):
```bash ```bash
cd resources cd resources
mkdir -p admin mkdir -p admin
@@ -244,26 +254,29 @@ Download the release from [release](https://github.com/lejianwen/rustdesk-api/re
npm run build npm run build
cp -ar dist/* ../admin/ cp -ar dist/* ../admin/
``` ```
4. 运行
4. Run:
```bash ```bash
# Run directly #直接运行
go run cmd/apimain.go go run cmd/apimain.go
# Or generate and run the API using generate_api.go #或者使用generate_api.go生成api并运行
go generate generate_api.go 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`
目录下生成对应的可执行文件。直接运行编译后的可执行文件即可。
5. To compile, change to the project root directory. For Windows, run `build.bat`, and for Linux, run `build.sh`. After 6. 打开浏览器访问`http://<your server[:port]>/_admin/`,默认用户名密码为`admin`,请及时更改密码。
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 #### 使用`lejianwen/server-s6`镜像运行
- Connection timeout issue resolved - 已解决链接超时问题
- Can enforce login before initiating a connection - 可以强制登录后才能发起链接
- github https://github.com/lejianwen/rustdesk-server - github https://github.com/lejianwen/rustdesk-server
```yaml ```yaml
@@ -299,19 +312,24 @@ Download the release from [release](https://github.com/lejianwen/rustdesk-api/re
restart: unless-stopped restart: unless-stopped
``` ```
## Others
## 其他
- [WIKI](https://github.com/lejianwen/rustdesk-api/wiki) - [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
- [Connection Timeout](https://github.com/lejianwen/rustdesk-api/issues/92) - [链接超时问题](https://github.com/lejianwen/rustdesk-api/issues/92)
- [Change client ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer) - [修改客户端ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)
- [Web client source](https://hub.docker.com/r/keyurbhole/flutter_web_desk) - [webclient来源](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"> <a href="https://github.com/lejianwen/rustdesk-api/graphs/contributors">
<img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" /> <img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" />
</a> </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
+74 -28
View File
@@ -1,6 +1,11 @@
package main package main
import ( import (
"fmt"
"os"
"strconv"
"time"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"github.com/lejianwen/rustdesk-api/v2/config" "github.com/lejianwen/rustdesk-api/v2/config"
"github.com/lejianwen/rustdesk-api/v2/global" "github.com/lejianwen/rustdesk-api/v2/global"
@@ -16,13 +21,13 @@ import (
"github.com/lejianwen/rustdesk-api/v2/utils" "github.com/lejianwen/rustdesk-api/v2/utils"
"github.com/nicksnyder/go-i18n/v2/i18n" "github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"os"
"strconv"
) )
// @title 管理系统API const DatabaseVersion = 265
// @title Management System API
// @version 1.0 // @version 1.0
// @description 接口 // @description API
// @basePath /api // @basePath /api
// @securityDefinitions.apikey token // @securityDefinitions.apikey token
// @in header // @in header
@@ -106,10 +111,10 @@ func main() {
} }
func InitGlobal() { func InitGlobal() {
//配置解析 // config parsing
global.Viper = config.Init(&global.Config, global.ConfigPath) global.Viper = config.Init(&global.Config, global.ConfigPath)
//日志 // logging
global.Logger = logger.New(&logger.Config{ global.Logger = logger.New(&logger.Config{
Path: global.Config.Logger.Path, Path: global.Config.Logger.Path,
Level: global.Config.Logger.Level, Level: global.Config.Logger.Level,
@@ -139,18 +144,41 @@ func InitGlobal() {
} }
//gorm //gorm
if global.Config.Gorm.Type == config.TypeMysql { 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{ global.DB = orm.NewMysql(&orm.MysqlConfig{
Dns: dns, Dsn: dsn,
MaxIdleConns: global.Config.Gorm.MaxIdleConns, MaxIdleConns: global.Config.Gorm.MaxIdleConns,
MaxOpenConns: global.Config.Gorm.MaxOpenConns, 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 { } else {
//sqlite //sqlite
global.DB = orm.NewSqlite(&orm.SqliteConfig{ global.DB = orm.NewSqlite(&orm.SqliteConfig{
MaxIdleConns: global.Config.Gorm.MaxIdleConns, MaxIdleConns: global.Config.Gorm.MaxIdleConns,
MaxOpenConns: global.Config.Gorm.MaxOpenConns, MaxOpenConns: global.Config.Gorm.MaxOpenConns,
}) }, global.Logger)
} }
//validator //validator
@@ -175,33 +203,47 @@ func InitGlobal() {
//service //service
service.New(&global.Config, global.DB, global.Logger, global.Jwt, global.Lock) service.New(&global.Config, global.DB, global.Logger, global.Jwt, global.Lock)
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.LoginLimiter.RegisterProvider(utils.B64StringCaptchaProvider{})
DatabaseAutoUpdate() DatabaseAutoUpdate()
} }
func DatabaseAutoUpdate() { func DatabaseAutoUpdate() {
version := 262 version := DatabaseVersion
db := global.DB db := global.DB
if global.Config.Gorm.Type == config.TypeMysql { if global.Config.Gorm.Type == config.TypeMysql {
//检查存不存在数据库,不存在则创建 // check whether the database exists; create it if not
dbName := db.Migrator().CurrentDatabase() dbName := db.Migrator().CurrentDatabase()
if dbName == "" { if dbName == "" {
dbName = global.Config.Mysql.Dbname dbName = global.Config.Mysql.Dbname
// 移除 DSN 中的数据库名称,以便初始连接时不指定数据库 // remove the database name from the DSN so the initial connection does not specify a database
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,
"",
)
// new connection
dbWithoutDB := orm.NewMysql(&orm.MysqlConfig{ dbWithoutDB := orm.NewMysql(&orm.MysqlConfig{
Dns: dsnWithoutDB, Dsn: dsnWithoutDB,
}) }, global.Logger)
// 获取底层的 *sql.DB 对象,并确保在程序退出时关闭连接 // get the underlying *sql.DB object and ensure the connection is closed when the program exits
sqlDBWithoutDB, err := dbWithoutDB.DB() sqlDBWithoutDB, err := dbWithoutDB.DB()
if err != nil { if err != nil {
global.Logger.Errorf("获取底层 *sql.DB 对象失败: %v", err) global.Logger.Errorf("failed to get underlying *sql.DB object: %v", err)
return return
} }
defer func() { defer func() {
if err := sqlDBWithoutDB.Close(); err != nil { if err := sqlDBWithoutDB.Close(); err != nil {
global.Logger.Errorf("关闭连接失败: %v", err) global.Logger.Errorf("failed to close connection: %v", err)
} }
}() }()
@@ -216,20 +258,20 @@ func DatabaseAutoUpdate() {
if !db.Migrator().HasTable(&model.Version{}) { if !db.Migrator().HasTable(&model.Version{}) {
Migrate(uint(version)) Migrate(uint(version))
} else { } else {
//查找最后一个version // find the last version
var v model.Version var v model.Version
db.Last(&v) db.Last(&v)
if v.Version < uint(version) { if v.Version < uint(version) {
Migrate(uint(version)) Migrate(uint(version))
} }
// 245迁移 // 245 migration
if v.Version < 245 { if v.Version < 245 {
//oauths 表的 oauth_type 字段设置为 op同样的值 // set the oauths table's oauth_type field to the same value as op
db.Exec("update oauths set 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 oauths set issuer = 'https://accounts.google.com' where op = 'google'")
db.Exec("update user_thirds set oauth_type = third_type, op = third_type") db.Exec("update user_thirds set oauth_type = third_type, op = third_type")
//通过email迁移旧的google授权 // migrate old google authorizations by email
uts := make([]model.UserThird, 0) uts := make([]model.UserThird, 0)
db.Where("oauth_type = ?", "google").Find(&uts) db.Where("oauth_type = ?", "google").Find(&uts)
for _, ut := range uts { for _, ut := range uts {
@@ -269,7 +311,7 @@ func Migrate(version uint) {
global.Logger.Error("migrate err :=>", err) global.Logger.Error("migrate err :=>", err)
} }
global.DB.Create(&model.Version{Version: version}) global.DB.Create(&model.Version{Version: version})
//如果是初次则创建一个默认用户 // if this is the first run, create a default user
var vc int64 var vc int64
global.DB.Model(&model.Version{}).Count(&vc) global.DB.Model(&model.Version{}).Count(&vc)
if vc == 1 { if vc == 1 {
@@ -291,7 +333,7 @@ func Migrate(version uint) {
Type: model.GroupTypeShare, Type: model.GroupTypeShare,
} }
service.AllService.GroupService.Create(groupShare) service.AllService.GroupService.Create(groupShare)
//true // is true
is_admin := true is_admin := true
admin := &model.User{ admin := &model.User{
Username: "admin", Username: "admin",
@@ -301,10 +343,14 @@ func Migrate(version uint) {
GroupId: 1, GroupId: 1,
} }
// 生成随机密码 // generate a random password
pwd := utils.RandomString(8) pwd := utils.RandomString(8)
global.Logger.Info("Admin Password Is: ", pwd) global.Logger.Info("Admin Password Is: ", pwd)
admin.Password = service.AllService.UserService.EncryptPassword(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) global.DB.Create(admin)
} }
-1
View File
@@ -1 +0,0 @@
### 👏👏👏 你好 ***{{username}}*** 欢迎使用 [RustDesk API](https://github.com/lejianwen/rustdesk-api)
+27 -27
View File
@@ -1,19 +1,26 @@
lang: "zh-CN" lang: "en"
app: app:
web-client: 1 # 1:启用 0:禁用 web-client: 1 # 1:enable 0:disable
register: false #是否开启注册 register: false #whether registration is enabled
show-swagger: 0 # 1:启用 0:禁用 register-status: 1 # default status for registered users 1:enable 2:disable
captcha-threshold: 3 # <0:disabled, 0 always, >0:enabled
ban-threshold: 0 # 0:disabled, >0:enabled
show-swagger: 0 # 1:enable 0:disable
token-expire: 168h token-expire: 168h
web-sso: true #web auth sso web-sso: true #web auth sso
disable-pwd-login: false #禁用密码登录 disable-pwd-login: false #disable password login
admin: admin:
title: "RustDesk Api Admin" title: "RustDesk API Admin"
hello-file: "./conf/admin/hello.html" #优先使用file hello-file: "./conf/admin/hello.html" #prefer to use a file for the welcome message
hello: "" 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: gin:
api-addr: "0.0.0.0:21114" api-addr: "0.0.0.0:21114"
mode: "release" #release,debug,test mode: "release" #release,debug,test
resources-path: 'resources' #对外静态文件目录 resources-path: 'resources' #public static file directory
trust-proxy: "" trust-proxy: ""
gorm: gorm:
type: "sqlite" type: "sqlite"
@@ -24,6 +31,17 @@ mysql:
password: "" password: ""
addr: "" addr: ""
dbname: "" 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: rustdesk:
id-server: "192.168.1.66:21116" id-server: "192.168.1.66:21116"
relay-server: "192.168.1.66:21117" relay-server: "192.168.1.66:21117"
@@ -63,22 +81,4 @@ ldap:
last-name: "sn" 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. 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. 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.
redis:
addr: "127.0.0.1:6379"
password: ""
db: 0
cache:
type: "file"
file-dir: "./runtime/cache"
redis-addr: "127.0.0.1:6379"
redis-pwd: ""
redis-db: 0
oss:
access-key-id: ""
access-key-secret: ""
host: ""
callback-url: ""
expire-time: 30
max-byte: 10240
+43 -28
View File
@@ -14,36 +14,51 @@ const (
) )
type App struct { type App struct {
WebClient int `mapstructure:"web-client"` WebClient int `mapstructure:"web-client"`
Register bool `mapstructure:"register"` Register bool `mapstructure:"register"`
ShowSwagger int `mapstructure:"show-swagger"` RegisterStatus int `mapstructure:"register-status"`
TokenExpire time.Duration `mapstructure:"token-expire"` ShowSwagger int `mapstructure:"show-swagger"`
WebSso bool `mapstructure:"web-sso"` TokenExpire time.Duration `mapstructure:"token-expire"`
DisablePwdLogin bool `mapstructure:"disable-pwd-login"` WebSso bool `mapstructure:"web-sso"`
DisablePwdLogin bool `mapstructure:"disable-pwd-login"`
CaptchaThreshold int `mapstructure:"captcha-threshold"`
BanThreshold int `mapstructure:"ban-threshold"`
} }
type Admin struct { type Admin struct {
Title string `mapstructure:"title"` Title string `mapstructure:"title"`
Hello string `mapstructure:"hello"` Hello string `mapstructure:"hello"`
HelloFile string `mapstructure:"hello-file"` HelloFile string `mapstructure:"hello-file"`
IdServerPort int `mapstructure:"id-server-port"`
RelayServerPort int `mapstructure:"relay-server-port"`
} }
type Config struct { type Config struct {
Lang string `mapstructure:"lang"` Lang string `mapstructure:"lang"`
App App App App
Admin Admin Admin Admin
Gorm Gorm Gorm Gorm
Mysql Mysql Mysql Mysql
Gin Gin Postgresql Postgresql
Logger Logger Gin Gin
Redis Redis Logger Logger
Cache Cache Redis Redis
Oss Oss Cache Cache
Jwt Jwt Oss Oss
Rustdesk Rustdesk Jwt Jwt
Proxy Proxy Rustdesk Rustdesk
Ldap Ldap Proxy Proxy
Ldap Ldap
} }
// Init 初始化配置 func (a *Admin) Init() {
if a.IdServerPort == 0 {
a.IdServerPort = DefaultIdServerPort
}
if a.RelayServerPort == 0 {
a.RelayServerPort = DefaultRelayServerPort
}
}
// Init initializes the configuration
func Init(rowVal *Config, path string) *viper.Viper { func Init(rowVal *Config, path string) *viper.Viper {
if path == "" { if path == "" {
path = DefaultConfig path = DefaultConfig
@@ -62,9 +77,9 @@ func Init(rowVal *Config, path string) *viper.Viper {
v.WatchConfig() v.WatchConfig()
//监听配置修改没什么必要 // watching for config changes is not really necessary
v.OnConfigChange(func(e fsnotify.Event) { v.OnConfigChange(func(e fsnotify.Event) {
//配置文件修改监听 // watch for config file changes
fmt.Println("config file changed:", e.Name) fmt.Println("config file changed:", e.Name)
if err2 := v.Unmarshal(rowVal); err2 != nil { if err2 := v.Unmarshal(rowVal); err2 != nil {
fmt.Println(err2) fmt.Println(err2)
@@ -77,11 +92,11 @@ func Init(rowVal *Config, path string) *viper.Viper {
panic(fmt.Errorf("Fatal error config: %s \n", err)) panic(fmt.Errorf("Fatal error config: %s \n", err))
} }
rowVal.Rustdesk.LoadKeyFile() rowVal.Rustdesk.LoadKeyFile()
rowVal.Rustdesk.ParsePort() rowVal.Admin.Init()
return v return v
} }
// ReadEnv 读取环境变量 // ReadEnv reads environment variables
func ReadEnv(rowVal interface{}) *viper.Viper { func ReadEnv(rowVal interface{}) *viper.Viper {
v := viper.New() v := viper.New()
v.AutomaticEnv() v.AutomaticEnv()
+14 -2
View File
@@ -1,8 +1,9 @@
package config package config
const ( const (
TypeSqlite = "sqlite" TypeSqlite = "sqlite"
TypeMysql = "mysql" TypeMysql = "mysql"
TypePostgresql = "postgresql"
) )
type Gorm struct { type Gorm struct {
@@ -16,4 +17,15 @@ type Mysql struct {
Username string `mapstructure:"username"` Username string `mapstructure:"username"`
Password string `mapstructure:"password"` Password string `mapstructure:"password"`
Dbname string `mapstructure:"dbname"` 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"
} }
+1
View File
@@ -11,6 +11,7 @@ type LdapUser struct {
LastName string `mapstructure:"last-name"` LastName string `mapstructure:"last-name"`
Sync bool `mapstructure:"sync"` // Will sync the user's information to the internal database 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 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 { // type LdapGroup struct {
+5 -3
View File
@@ -3,18 +3,20 @@ package config
type GithubOauth struct { type GithubOauth struct {
ClientId string `mapstructure:"client-id"` ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"` ClientSecret string `mapstructure:"client-secret"`
RedirectUrl string `mapstructure:"redirect-url"`
} }
type GoogleOauth struct { type GoogleOauth struct {
ClientId string `mapstructure:"client-id"` ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"` ClientSecret string `mapstructure:"client-secret"`
RedirectUrl string `mapstructure:"redirect-url"`
} }
type OidcOauth struct { type OidcOauth struct {
Issuer string `mapstructure:"issuer"` Issuer string `mapstructure:"issuer"`
ClientId string `mapstructure:"client-id"` ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"` ClientSecret string `mapstructure:"client-secret"`
RedirectUrl string `mapstructure:"redirect-url"` }
type LinuxdoOauth struct {
ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"`
} }
-18
View File
@@ -2,8 +2,6 @@ package config
import ( import (
"os" "os"
"strconv"
"strings"
) )
const ( const (
@@ -40,19 +38,3 @@ func (rd *Rustdesk) LoadKeyFile() {
return return
} }
} }
func (rd *Rustdesk) ParsePort() {
// Parse port
idres := strings.Split(rd.IdServer, ":")
if len(idres) == 1 {
rd.IdServerPort = DefaultIdServerPort
} else if len(idres) == 2 {
rd.IdServerPort, _ = strconv.Atoi(idres[1])
}
relayres := strings.Split(rd.RelayServer, ":")
if len(relayres) == 1 {
rd.RelayServerPort = DefaultRelayServerPort
} else if len(relayres) == 2 {
rd.RelayServerPort, _ = strconv.Atoi(relayres[1])
}
}
View File
+4 -4
View File
@@ -5,7 +5,7 @@ services:
dockerfile: Dockerfile.dev dockerfile: Dockerfile.dev
args: args:
COUNTRY: CN COUNTRY: CN
FREONTEND_GIT_REPO: https://github.com/lejianwen/rustdesk-api-web.git FRONTEND_GIT_REPO: https://github.com/lejianwen/rustdesk-api-web.git
FRONTEND_GIT_BRANCH: master FRONTEND_GIT_BRANCH: master
# image: lejianwen/rustdesk-api # image: lejianwen/rustdesk-api
container_name: rustdesk-api container_name: rustdesk-api
@@ -18,7 +18,7 @@ services:
ports: ports:
- 21114:21114 - 21114:21114
volumes: volumes:
- ./data/rustdesk/api:/app/data #将数据库挂载出来方便备份 - ./data/rustdesk/api:/app/data #mount the database out for easy backup
- ./conf:/app/conf # config - ./conf:/app/conf # config
# - ./resources:/app/resources # 静态资源 # - ./resources:/app/resources # static resources
restart: unless-stopped restart: unless-stopped
+1 -1
View File
@@ -13,5 +13,5 @@ services:
volumes: volumes:
- ./data/rustdesk/api:/app/data # database - ./data/rustdesk/api:/app/data # database
# - ./conf:/app/conf # config # - ./conf:/app/conf # config
# - ./resources:/app/resources # 静态资源 # - ./resources:/app/resources # static resources
restart: unless-stopped restart: unless-stopped
+64
View File
@@ -0,0 +1,64 @@
# RustDesk full stack (server + relay + API/Web Admin) — self-contained example.
#
# Copy to docker-compose.yml and adjust the public IP / keys / secrets:
# cp docker-compose.yml.example docker-compose.yml
#
# Everything is inline (no .env). The Ed25519 key below is an EXAMPLE — generate
# your own for production. The two keys belong together:
# - hbbs/hbbr get the SECRET key via -k
# - rustdesk-api gets the matching PUBLIC key via RUSTDESK_API_RUSTDESK_KEY
# (the public key is also what clients see as their "Key").
services:
hbbs:
container_name: hbbs
image: rustdesk/rustdesk-server:latest
command: hbbs -r 194.164.204.114:21117 -k 1jqLELJaIjaRtt1Tcj6BhqJ7DhT9WszBH/XUHrqJdhdg4K98HmANQFOpUx/vPZFRWnHPTYYHyIB8uH3Xxr8PTw==
environment:
- TZ=Europe/Berlin
- ENCRYPTED_ONLY=1
- MUST_LOGIN=Y
volumes:
- ./data/server:/root
ports:
- "21115:21115"
- "21116:21116"
- "21116:21116/udp"
- "21118:21118"
depends_on:
- hbbr
restart: unless-stopped
hbbr:
container_name: hbbr
image: rustdesk/rustdesk-server:latest
command: hbbr -k 1jqLELJaIjaRtt1Tcj6BhqJ7DhT9WszBH/XUHrqJdhdg4K98HmANQFOpUx/vPZFRWnHPTYYHyIB8uH3Xxr8PTw==
environment:
- TZ=Europe/Berlin
volumes:
- ./data/server:/root
ports:
- "21117:21117"
- "21119:21119"
restart: unless-stopped
rustdesk-api:
container_name: rustdesk-api
image: git.hackner.dev/thomas/rustdesk-api:latest
command: ["./apimain"]
environment:
- TZ=Europe/Berlin
- RUSTDESK_API_LANG=en
- RUSTDESK_API_RUSTDESK_ID_SERVER=194.164.204.114:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=194.164.204.114:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://194.164.204.114:21114
- RUSTDESK_API_RUSTDESK_KEY=YOCvfB5gDUBTqVMf7z2RUVpxz02GB8iAfLh918a/D08=
- RUSTDESK_API_JWT_KEY=z5rt4z465rtzdh5sudtuzrutd66zz6
ports:
- "21114:21114"
volumes:
- ./api:/app/data
depends_on:
- hbbs
- hbbr
restart: unless-stopped
+432 -436
View File
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
+179 -150
View File
@@ -17,7 +17,7 @@ const docTemplateapi = `{
"paths": { "paths": {
"/": { "/": {
"get": { "get": {
"description": "首页", "description": "home",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -25,9 +25,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"首页" "home"
], ],
"summary": "首页", "summary": "home",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -51,7 +51,7 @@ const docTemplateapi = `{
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "地址列表", "description": "address list",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -59,9 +59,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址" "address"
], ],
"summary": "地址列表", "summary": "address list",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -83,7 +83,7 @@ const docTemplateapi = `{
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "地址更新", "description": "address update",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -91,12 +91,12 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址" "address"
], ],
"summary": "地址更新", "summary": "address update",
"parameters": [ "parameters": [
{ {
"description": "地址表单", "description": "address form",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -128,7 +128,7 @@ const docTemplateapi = `{
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "添加地址", "description": "add address",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -136,9 +136,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "添加地址", "summary": "add address",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -169,7 +169,7 @@ const docTemplateapi = `{
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "删除地址", "description": "delete address",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -177,9 +177,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "删除地址", "summary": "delete address",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -212,7 +212,7 @@ const docTemplateapi = `{
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "更新地址", "description": "update address",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -220,9 +220,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "更新地址", "summary": "update address",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -255,7 +255,7 @@ const docTemplateapi = `{
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "地址", "description": "address",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -263,19 +263,19 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "地址列表", "summary": "address list",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
"description": "页码", "description": "page number",
"name": "current", "name": "current",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "每页数量", "description": "items per page",
"name": "pageSize", "name": "pageSize",
"in": "query" "in": "query"
}, },
@@ -309,7 +309,7 @@ const docTemplateapi = `{
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "个人地址", "description": "personal address",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -317,9 +317,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "个人地址", "summary": "personal address",
"parameters": [ "parameters": [
{ {
"description": "string valid", "description": "string valid",
@@ -353,7 +353,7 @@ const docTemplateapi = `{
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "设置", "description": "settings",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -361,9 +361,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "设置", "summary": "settings",
"parameters": [ "parameters": [
{ {
"description": "string valid", "description": "string valid",
@@ -397,7 +397,7 @@ const docTemplateapi = `{
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "共享", "description": "shared",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -405,19 +405,19 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "共享地址簿", "summary": "shared address book",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
"description": "页码", "description": "page number",
"name": "current", "name": "current",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "每页数量", "description": "items per page",
"name": "pageSize", "name": "pageSize",
"in": "query" "in": "query"
} }
@@ -445,7 +445,7 @@ const docTemplateapi = `{
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "标签", "description": "tag",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -453,9 +453,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "标签添加", "summary": "add tag",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -488,7 +488,7 @@ const docTemplateapi = `{
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "标签", "description": "tag",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -496,9 +496,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "标签重命名", "summary": "rename tag",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -531,7 +531,7 @@ const docTemplateapi = `{
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "标签", "description": "tag",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -539,9 +539,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "标签修改颜色", "summary": "change tag color",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -574,7 +574,7 @@ const docTemplateapi = `{
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "标签", "description": "tag",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -582,9 +582,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "标签删除", "summary": "delete tag",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -617,7 +617,7 @@ const docTemplateapi = `{
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "标签", "description": "tag",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -625,9 +625,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "标签", "summary": "tag",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -655,7 +655,7 @@ const docTemplateapi = `{
}, },
"/audit/conn": { "/audit/conn": {
"post": { "post": {
"description": "审计连接", "description": "audit connection",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -663,12 +663,12 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"审计" "audit"
], ],
"summary": "审计连接", "summary": "audit connection",
"parameters": [ "parameters": [
{ {
"description": "审计连接", "description": "audit connection",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -695,7 +695,7 @@ const docTemplateapi = `{
}, },
"/audit/file": { "/audit/file": {
"post": { "post": {
"description": "审计文件", "description": "audit file",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -703,12 +703,12 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"审计" "audit"
], ],
"summary": "审计文件", "summary": "audit file",
"parameters": [ "parameters": [
{ {
"description": "审计文件", "description": "audit file",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -740,7 +740,7 @@ const docTemplateapi = `{
"token": [] "token": []
} }
], ],
"description": "用户信息", "description": "user info",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -748,9 +748,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"用户" "user"
], ],
"summary": "用户信息", "summary": "user info",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -774,7 +774,7 @@ const docTemplateapi = `{
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "机器", "description": "machine",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -782,25 +782,25 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"群组" "group"
], ],
"summary": "设备", "summary": "device",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
"description": "页码", "description": "page number",
"name": "page", "name": "page",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "每页数量", "description": "items per page",
"name": "pageSize", "name": "pageSize",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "状态", "description": "status",
"name": "status", "name": "status",
"in": "query" "in": "query"
}, },
@@ -829,7 +829,7 @@ const docTemplateapi = `{
}, },
"/heartbeat": { "/heartbeat": {
"post": { "post": {
"description": "心跳", "description": "heartbeat",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -837,9 +837,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"首页" "home"
], ],
"summary": "心跳", "summary": "heartbeat",
"responses": { "responses": {
"200": { "200": {
"description": "OK" "description": "OK"
@@ -855,7 +855,7 @@ const docTemplateapi = `{
}, },
"/login": { "/login": {
"post": { "post": {
"description": "登录", "description": "login",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -863,12 +863,12 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"登录" "login"
], ],
"summary": "登录", "summary": "login",
"parameters": [ "parameters": [
{ {
"description": "登录表单", "description": "login form",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -895,7 +895,7 @@ const docTemplateapi = `{
}, },
"/login-options": { "/login-options": {
"get": { "get": {
"description": "登录选项", "description": "login options",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -903,9 +903,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"登录" "login"
], ],
"summary": "登录选项", "summary": "login options",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -927,7 +927,7 @@ const docTemplateapi = `{
}, },
"/logout": { "/logout": {
"post": { "post": {
"description": "登出", "description": "logout",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -935,9 +935,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"登录" "login"
], ],
"summary": "登出", "summary": "logout",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -954,35 +954,6 @@ const docTemplateapi = `{
} }
} }
}, },
"/oauth/callback": {
"get": {
"description": "OauthCallback",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "OauthCallback",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.LoginRes"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/oidc/auth": { "/oidc/auth": {
"post": { "post": {
"description": "OidcAuth", "description": "OidcAuth",
@@ -1041,14 +1012,9 @@ const docTemplateapi = `{
} }
} }
}, },
"/peers": { "/oidc/callback": {
"get": { "get": {
"security": [ "description": "OauthCallback",
{
"BearerAuth": []
}
],
"description": "机器",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1056,25 +1022,59 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"群组" "Oauth"
], ],
"summary": "机器", "summary": "OauthCallback",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.LoginRes"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/peers": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "machine",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"group"
],
"summary": "machine",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
"description": "页码", "description": "page number",
"name": "page", "name": "page",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "每页数量", "description": "items per page",
"name": "pageSize", "name": "pageSize",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "状态", "description": "status",
"name": "status", "name": "status",
"in": "query" "in": "query"
}, },
@@ -1108,7 +1108,7 @@ const docTemplateapi = `{
"token": [] "token": []
} }
], ],
"description": "服务配置,给webclient提供api-server", "description": "service config, provides an api-server for the webclient",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1118,7 +1118,7 @@ const docTemplateapi = `{
"tags": [ "tags": [
"WEBCLIENT" "WEBCLIENT"
], ],
"summary": "服务配置", "summary": "service config",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -1142,7 +1142,7 @@ const docTemplateapi = `{
"token": [] "token": []
} }
], ],
"description": "服务配置,给webclient提供api-server", "description": "service config, provides an api-server for the webclient",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1152,7 +1152,7 @@ const docTemplateapi = `{
"tags": [ "tags": [
"WEBCLIENT_V2" "WEBCLIENT_V2"
], ],
"summary": "服务配置", "summary": "service config",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -1171,7 +1171,7 @@ const docTemplateapi = `{
}, },
"/shared-peer": { "/shared-peer": {
"post": { "post": {
"description": "分享的peer", "description": "shared peer",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1181,7 +1181,7 @@ const docTemplateapi = `{
"tags": [ "tags": [
"WEBCLIENT" "WEBCLIENT"
], ],
"summary": "分享的peer", "summary": "shared peer",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -1200,7 +1200,7 @@ const docTemplateapi = `{
}, },
"/sysinfo": { "/sysinfo": {
"post": { "post": {
"description": "提交系统信息", "description": "submit system info",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1208,12 +1208,12 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址" "System"
], ],
"summary": "提交系统信息", "summary": "submit system info",
"parameters": [ "parameters": [
{ {
"description": "系统信息表单", "description": "system info form",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -1238,14 +1238,9 @@ const docTemplateapi = `{
} }
} }
}, },
"/users": { "/sysinfo_ver": {
"get": { "post": {
"security": [ "description": "get system version info",
{
"BearerAuth": []
}
],
"description": "用户列表",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1253,25 +1248,59 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"群组" "System"
], ],
"summary": "用户列表", "summary": "get system version info",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/users": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "user list",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"group"
],
"summary": "user list",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
"description": "页码", "description": "page number",
"name": "page", "name": "page",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "每页数量", "description": "items per page",
"name": "pageSize", "name": "pageSize",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "状态", "description": "status",
"name": "status", "name": "status",
"in": "query" "in": "query"
}, },
@@ -1315,7 +1344,7 @@ const docTemplateapi = `{
}, },
"/version": { "/version": {
"get": { "get": {
"description": "版本", "description": "version",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1323,9 +1352,9 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"首页" "home"
], ],
"summary": "版本", "summary": "version",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -1564,7 +1593,7 @@ const docTemplateapi = `{
"type": "integer" "type": "integer"
}, },
"color": { "color": {
"description": "color flutter的颜色值,从0x00000000 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色, 可以转成rgba", "description": "color is flutter's color value,from 0x00000000 to 0xFFFFFFFF; the first two digits represent transparency, the following 6 digits represent the color, can be converted to rgba",
"type": "integer" "type": "integer"
}, },
"created_at": { "created_at": {
@@ -1654,8 +1683,8 @@ var SwaggerInfoapi = &swag.Spec{
Host: "", Host: "",
BasePath: "/api", BasePath: "/api",
Schemes: []string{}, Schemes: []string{},
Title: "管理系统API", Title: "management systemAPI",
Description: "接口", Description: "API",
InfoInstanceName: "api", InfoInstanceName: "api",
SwaggerTemplate: docTemplateapi, SwaggerTemplate: docTemplateapi,
LeftDelim: "{{", LeftDelim: "{{",
+179 -150
View File
@@ -1,8 +1,8 @@
{ {
"swagger": "2.0", "swagger": "2.0",
"info": { "info": {
"description": "接口", "description": "API",
"title": "管理系统API", "title": "management systemAPI",
"contact": {}, "contact": {},
"version": "1.0" "version": "1.0"
}, },
@@ -10,7 +10,7 @@
"paths": { "paths": {
"/": { "/": {
"get": { "get": {
"description": "首页", "description": "home",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -18,9 +18,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"首页" "home"
], ],
"summary": "首页", "summary": "home",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -44,7 +44,7 @@
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "地址列表", "description": "address list",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -52,9 +52,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址" "address"
], ],
"summary": "地址列表", "summary": "address list",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -76,7 +76,7 @@
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "地址更新", "description": "address update",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -84,12 +84,12 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址" "address"
], ],
"summary": "地址更新", "summary": "address update",
"parameters": [ "parameters": [
{ {
"description": "地址表单", "description": "address form",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -121,7 +121,7 @@
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "添加地址", "description": "add address",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -129,9 +129,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "添加地址", "summary": "add address",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -162,7 +162,7 @@
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "删除地址", "description": "delete address",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -170,9 +170,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "删除地址", "summary": "delete address",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -205,7 +205,7 @@
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "更新地址", "description": "update address",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -213,9 +213,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "更新地址", "summary": "update address",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -248,7 +248,7 @@
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "地址", "description": "address",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -256,19 +256,19 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "地址列表", "summary": "address list",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
"description": "页码", "description": "page number",
"name": "current", "name": "current",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "每页数量", "description": "items per page",
"name": "pageSize", "name": "pageSize",
"in": "query" "in": "query"
}, },
@@ -302,7 +302,7 @@
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "个人地址", "description": "personal address",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -310,9 +310,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "个人地址", "summary": "personal address",
"parameters": [ "parameters": [
{ {
"description": "string valid", "description": "string valid",
@@ -346,7 +346,7 @@
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "设置", "description": "settings",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -354,9 +354,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "设置", "summary": "settings",
"parameters": [ "parameters": [
{ {
"description": "string valid", "description": "string valid",
@@ -390,7 +390,7 @@
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "共享", "description": "shared",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -398,19 +398,19 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "共享地址簿", "summary": "shared address book",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
"description": "页码", "description": "page number",
"name": "current", "name": "current",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "每页数量", "description": "items per page",
"name": "pageSize", "name": "pageSize",
"in": "query" "in": "query"
} }
@@ -438,7 +438,7 @@
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "标签", "description": "tag",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -446,9 +446,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "标签添加", "summary": "add tag",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -481,7 +481,7 @@
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "标签", "description": "tag",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -489,9 +489,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "标签重命名", "summary": "rename tag",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -524,7 +524,7 @@
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "标签", "description": "tag",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -532,9 +532,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "标签修改颜色", "summary": "change tag color",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -567,7 +567,7 @@
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "标签", "description": "tag",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -575,9 +575,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "标签删除", "summary": "delete tag",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -610,7 +610,7 @@
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "标签", "description": "tag",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -618,9 +618,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址[Personal]" "address[Personal]"
], ],
"summary": "标签", "summary": "tag",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@@ -648,7 +648,7 @@
}, },
"/audit/conn": { "/audit/conn": {
"post": { "post": {
"description": "审计连接", "description": "audit connection",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -656,12 +656,12 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"审计" "audit"
], ],
"summary": "审计连接", "summary": "audit connection",
"parameters": [ "parameters": [
{ {
"description": "审计连接", "description": "audit connection",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -688,7 +688,7 @@
}, },
"/audit/file": { "/audit/file": {
"post": { "post": {
"description": "审计文件", "description": "audit file",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -696,12 +696,12 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"审计" "audit"
], ],
"summary": "审计文件", "summary": "audit file",
"parameters": [ "parameters": [
{ {
"description": "审计文件", "description": "audit file",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -733,7 +733,7 @@
"token": [] "token": []
} }
], ],
"description": "用户信息", "description": "user info",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -741,9 +741,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"用户" "user"
], ],
"summary": "用户信息", "summary": "user info",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -767,7 +767,7 @@
"BearerAuth": [] "BearerAuth": []
} }
], ],
"description": "机器", "description": "machine",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -775,25 +775,25 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"群组" "group"
], ],
"summary": "设备", "summary": "device",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
"description": "页码", "description": "page number",
"name": "page", "name": "page",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "每页数量", "description": "items per page",
"name": "pageSize", "name": "pageSize",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "状态", "description": "status",
"name": "status", "name": "status",
"in": "query" "in": "query"
}, },
@@ -822,7 +822,7 @@
}, },
"/heartbeat": { "/heartbeat": {
"post": { "post": {
"description": "心跳", "description": "heartbeat",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -830,9 +830,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"首页" "home"
], ],
"summary": "心跳", "summary": "heartbeat",
"responses": { "responses": {
"200": { "200": {
"description": "OK" "description": "OK"
@@ -848,7 +848,7 @@
}, },
"/login": { "/login": {
"post": { "post": {
"description": "登录", "description": "login",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -856,12 +856,12 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"登录" "login"
], ],
"summary": "登录", "summary": "login",
"parameters": [ "parameters": [
{ {
"description": "登录表单", "description": "login form",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -888,7 +888,7 @@
}, },
"/login-options": { "/login-options": {
"get": { "get": {
"description": "登录选项", "description": "login options",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -896,9 +896,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"登录" "login"
], ],
"summary": "登录选项", "summary": "login options",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -920,7 +920,7 @@
}, },
"/logout": { "/logout": {
"post": { "post": {
"description": "登出", "description": "logout",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -928,9 +928,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"登录" "login"
], ],
"summary": "登出", "summary": "logout",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -947,35 +947,6 @@
} }
} }
}, },
"/oauth/callback": {
"get": {
"description": "OauthCallback",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "OauthCallback",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.LoginRes"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/oidc/auth": { "/oidc/auth": {
"post": { "post": {
"description": "OidcAuth", "description": "OidcAuth",
@@ -1034,14 +1005,9 @@
} }
} }
}, },
"/peers": { "/oidc/callback": {
"get": { "get": {
"security": [ "description": "OauthCallback",
{
"BearerAuth": []
}
],
"description": "机器",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1049,25 +1015,59 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"群组" "Oauth"
], ],
"summary": "机器", "summary": "OauthCallback",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.LoginRes"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/peers": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "machine",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"group"
],
"summary": "machine",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
"description": "页码", "description": "page number",
"name": "page", "name": "page",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "每页数量", "description": "items per page",
"name": "pageSize", "name": "pageSize",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "状态", "description": "status",
"name": "status", "name": "status",
"in": "query" "in": "query"
}, },
@@ -1101,7 +1101,7 @@
"token": [] "token": []
} }
], ],
"description": "服务配置,给webclient提供api-server", "description": "service config, provides an api-server for the webclient",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1111,7 +1111,7 @@
"tags": [ "tags": [
"WEBCLIENT" "WEBCLIENT"
], ],
"summary": "服务配置", "summary": "service config",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -1135,7 +1135,7 @@
"token": [] "token": []
} }
], ],
"description": "服务配置,给webclient提供api-server", "description": "service config, provides an api-server for the webclient",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1145,7 +1145,7 @@
"tags": [ "tags": [
"WEBCLIENT_V2" "WEBCLIENT_V2"
], ],
"summary": "服务配置", "summary": "service config",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -1164,7 +1164,7 @@
}, },
"/shared-peer": { "/shared-peer": {
"post": { "post": {
"description": "分享的peer", "description": "shared peer",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1174,7 +1174,7 @@
"tags": [ "tags": [
"WEBCLIENT" "WEBCLIENT"
], ],
"summary": "分享的peer", "summary": "shared peer",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -1193,7 +1193,7 @@
}, },
"/sysinfo": { "/sysinfo": {
"post": { "post": {
"description": "提交系统信息", "description": "submit system info",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1201,12 +1201,12 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"地址" "System"
], ],
"summary": "提交系统信息", "summary": "submit system info",
"parameters": [ "parameters": [
{ {
"description": "系统信息表单", "description": "system info form",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -1231,14 +1231,9 @@
} }
} }
}, },
"/users": { "/sysinfo_ver": {
"get": { "post": {
"security": [ "description": "get system version info",
{
"BearerAuth": []
}
],
"description": "用户列表",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1246,25 +1241,59 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"群组" "System"
], ],
"summary": "用户列表", "summary": "get system version info",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/users": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "user list",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"group"
],
"summary": "user list",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
"description": "页码", "description": "page number",
"name": "page", "name": "page",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "每页数量", "description": "items per page",
"name": "pageSize", "name": "pageSize",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"description": "状态", "description": "status",
"name": "status", "name": "status",
"in": "query" "in": "query"
}, },
@@ -1308,7 +1337,7 @@
}, },
"/version": { "/version": {
"get": { "get": {
"description": "版本", "description": "version",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1316,9 +1345,9 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"首页" "home"
], ],
"summary": "版本", "summary": "version",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -1557,7 +1586,7 @@
"type": "integer" "type": "integer"
}, },
"color": { "color": {
"description": "color flutter的颜色值,从0x00000000 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色, 可以转成rgba", "description": "color is flutter's color value,from 0x00000000 to 0xFFFFFFFF; the first two digits represent transparency, the following 6 digits represent the color, can be converted to rgba",
"type": "integer" "type": "integer"
}, },
"created_at": { "created_at": {
+147 -128
View File
@@ -146,8 +146,8 @@ definitions:
collection_id: collection_id:
type: integer type: integer
color: color:
description: color flutter的颜色值,从0x00000000 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色, description: color is flutter's color value,from 0x00000000 to 0xFFFFFFFF; the first two digits represent transparency, the following 6 digits represent the color,
可以转成rgba can be converted to rgba
type: integer type: integer
created_at: created_at:
type: string type: string
@@ -194,15 +194,15 @@ definitions:
type: object type: object
info: info:
contact: {} contact: {}
description: 接口 description: API
title: 管理系统API title: management systemAPI
version: "1.0" version: "1.0"
paths: paths:
/: /:
get: get:
consumes: consumes:
- application/json - application/json
description: 首页 description: home
produces: produces:
- application/json - application/json
responses: responses:
@@ -214,14 +214,14 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
summary: 首页 summary: home
tags: tags:
- 首页 - home
/ab: /ab:
get: get:
consumes: consumes:
- application/json - application/json
description: 地址列表 description: address list
produces: produces:
- application/json - application/json
responses: responses:
@@ -235,15 +235,15 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 地址列表 summary: address list
tags: tags:
- 地址 - address
post: post:
consumes: consumes:
- application/json - application/json
description: 地址更新 description: address update
parameters: parameters:
- description: 地址表单 - description: address form
in: body in: body
name: body name: body
required: true required: true
@@ -262,14 +262,14 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 地址更新 summary: address update
tags: tags:
- 地址 - address
/ab/peer/add/{guid}: /ab/peer/add/{guid}:
delete: delete:
consumes: consumes:
- application/json - application/json
description: 删除地址 description: delete address
parameters: parameters:
- description: guid - description: guid
in: path in: path
@@ -289,13 +289,13 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 删除地址 summary: delete address
tags: tags:
- 地址[Personal] - address[Personal]
post: post:
consumes: consumes:
- application/json - application/json
description: 添加地址 description: add address
parameters: parameters:
- description: guid - description: guid
in: path in: path
@@ -315,14 +315,14 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 添加地址 summary: add address
tags: tags:
- 地址[Personal] - address[Personal]
/ab/peer/update/{guid}: /ab/peer/update/{guid}:
put: put:
consumes: consumes:
- application/json - application/json
description: 更新地址 description: update address
parameters: parameters:
- description: guid - description: guid
in: path in: path
@@ -342,20 +342,20 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 更新地址 summary: update address
tags: tags:
- 地址[Personal] - address[Personal]
/ab/peers: /ab/peers:
post: post:
consumes: consumes:
- application/json - application/json
description: 地址 description: address
parameters: parameters:
- description: 页码 - description: page number
in: query in: query
name: current name: current
type: integer type: integer
- description: 每页数量 - description: items per page
in: query in: query
name: pageSize name: pageSize
type: integer type: integer
@@ -376,14 +376,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 地址列表 summary: address list
tags: tags:
- 地址[Personal] - address[Personal]
/ab/personal: /ab/personal:
post: post:
consumes: consumes:
- application/json - application/json
description: 个人地址 description: personal address
parameters: parameters:
- description: string valid - description: string valid
in: body in: body
@@ -403,14 +403,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 个人地址 summary: personal address
tags: tags:
- 地址[Personal] - address[Personal]
/ab/settings: /ab/settings:
post: post:
consumes: consumes:
- application/json - application/json
description: 设置 description: settings
parameters: parameters:
- description: string valid - description: string valid
in: body in: body
@@ -430,20 +430,20 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 设置 summary: settings
tags: tags:
- 地址[Personal] - address[Personal]
/ab/shared/profiles: /ab/shared/profiles:
post: post:
consumes: consumes:
- application/json - application/json
description: 共享 description: shared
parameters: parameters:
- description: 页码 - description: page number
in: query in: query
name: current name: current
type: integer type: integer
- description: 每页数量 - description: items per page
in: query in: query
name: pageSize name: pageSize
type: integer type: integer
@@ -460,14 +460,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 共享地址簿 summary: shared address book
tags: tags:
- 地址[Personal] - address[Personal]
/ab/tag/{guid}: /ab/tag/{guid}:
delete: delete:
consumes: consumes:
- application/json - application/json
description: 标签 description: tag
parameters: parameters:
- description: guid - description: guid
in: path in: path
@@ -487,14 +487,14 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 标签删除 summary: delete tag
tags: tags:
- 地址[Personal] - address[Personal]
/ab/tag/add/{guid}: /ab/tag/add/{guid}:
post: post:
consumes: consumes:
- application/json - application/json
description: 标签 description: tag
parameters: parameters:
- description: guid - description: guid
in: path in: path
@@ -514,14 +514,14 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 标签添加 summary: add tag
tags: tags:
- 地址[Personal] - address[Personal]
/ab/tag/rename/{guid}: /ab/tag/rename/{guid}:
put: put:
consumes: consumes:
- application/json - application/json
description: 标签 description: tag
parameters: parameters:
- description: guid - description: guid
in: path in: path
@@ -541,14 +541,14 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 标签重命名 summary: rename tag
tags: tags:
- 地址[Personal] - address[Personal]
/ab/tag/update/{guid}: /ab/tag/update/{guid}:
put: put:
consumes: consumes:
- application/json - application/json
description: 标签 description: tag
parameters: parameters:
- description: guid - description: guid
in: path in: path
@@ -568,14 +568,14 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 标签修改颜色 summary: change tag color
tags: tags:
- 地址[Personal] - address[Personal]
/ab/tags/{guid}: /ab/tags/{guid}:
post: post:
consumes: consumes:
- application/json - application/json
description: 标签 description: tag
parameters: parameters:
- description: guid - description: guid
in: path in: path
@@ -595,16 +595,16 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 标签 summary: tag
tags: tags:
- 地址[Personal] - address[Personal]
/audit/conn: /audit/conn:
post: post:
consumes: consumes:
- application/json - application/json
description: 审计连接 description: audit connection
parameters: parameters:
- description: 审计连接 - description: audit connection
in: body in: body
name: body name: body
required: true required: true
@@ -621,16 +621,16 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
summary: 审计连接 summary: audit connection
tags: tags:
- 审计 - audit
/audit/file: /audit/file:
post: post:
consumes: consumes:
- application/json - application/json
description: 审计文件 description: audit file
parameters: parameters:
- description: 审计文件 - description: audit file
in: body in: body
name: body name: body
required: true required: true
@@ -647,14 +647,14 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
summary: 审计文件 summary: audit file
tags: tags:
- 审计 - audit
/currentUser: /currentUser:
get: get:
consumes: consumes:
- application/json - application/json
description: 用户信息 description: user info
produces: produces:
- application/json - application/json
responses: responses:
@@ -668,24 +668,24 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: 用户信息 summary: user info
tags: tags:
- 用户 - user
/device-group/accessible: /device-group/accessible:
get: get:
consumes: consumes:
- application/json - application/json
description: 机器 description: machine
parameters: parameters:
- description: 页码 - description: page number
in: query in: query
name: page name: page
type: integer type: integer
- description: 每页数量 - description: items per page
in: query in: query
name: pageSize name: pageSize
type: integer type: integer
- description: 状态 - description: status
in: query in: query
name: status name: status
type: integer type: integer
@@ -706,14 +706,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 设备 summary: device
tags: tags:
- 群组 - group
/heartbeat: /heartbeat:
post: post:
consumes: consumes:
- application/json - application/json
description: 心跳 description: heartbeat
produces: produces:
- application/json - application/json
responses: responses:
@@ -723,16 +723,16 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
summary: 心跳 summary: heartbeat
tags: tags:
- 首页 - home
/login: /login:
post: post:
consumes: consumes:
- application/json - application/json
description: 登录 description: login
parameters: parameters:
- description: 登录表单 - description: login form
in: body in: body
name: body name: body
required: true required: true
@@ -749,14 +749,14 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
summary: 登录 summary: login
tags: tags:
- 登录 - login
/login-options: /login-options:
get: get:
consumes: consumes:
- application/json - application/json
description: 登录选项 description: login options
produces: produces:
- application/json - application/json
responses: responses:
@@ -770,14 +770,14 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
summary: 登录选项 summary: login options
tags: tags:
- 登录 - login
/logout: /logout:
post: post:
consumes: consumes:
- application/json - application/json
description: 登出 description: logout
produces: produces:
- application/json - application/json
responses: responses:
@@ -789,28 +789,9 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
summary: 登出 summary: logout
tags: tags:
- 登录 - login
/oauth/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
/oidc/auth: /oidc/auth:
post: post:
consumes: consumes:
@@ -849,21 +830,40 @@ paths:
summary: OidcAuthQuery summary: OidcAuthQuery
tags: tags:
- Oauth - 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: /peers:
get: get:
consumes: consumes:
- application/json - application/json
description: 机器 description: machine
parameters: parameters:
- description: 页码 - description: page number
in: query in: query
name: page name: page
type: integer type: integer
- description: 每页数量 - description: items per page
in: query in: query
name: pageSize name: pageSize
type: integer type: integer
- description: 状态 - description: status
in: query in: query
name: status name: status
type: integer type: integer
@@ -884,14 +884,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 机器 summary: machine
tags: tags:
- 群组 - group
/server-config: /server-config:
get: get:
consumes: consumes:
- application/json - application/json
description: 服务配置,给webclient提供api-server description: service config, provides an api-server for the webclient
produces: produces:
- application/json - application/json
responses: responses:
@@ -905,14 +905,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: 服务配置 summary: service config
tags: tags:
- WEBCLIENT - WEBCLIENT
/server-config-v2: /server-config-v2:
get: get:
consumes: consumes:
- application/json - application/json
description: 服务配置,给webclient提供api-server description: service config, provides an api-server for the webclient
produces: produces:
- application/json - application/json
responses: responses:
@@ -926,14 +926,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: 服务配置 summary: service config
tags: tags:
- WEBCLIENT_V2 - WEBCLIENT_V2
/shared-peer: /shared-peer:
post: post:
consumes: consumes:
- application/json - application/json
description: 分享的peer description: shared peer
produces: produces:
- application/json - application/json
responses: responses:
@@ -945,16 +945,16 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
summary: 分享的peer summary: shared peer
tags: tags:
- WEBCLIENT - WEBCLIENT
/sysinfo: /sysinfo:
post: post:
consumes: consumes:
- application/json - application/json
description: 提交系统信息 description: submit system info
parameters: parameters:
- description: 系统信息表单 - description: system info form
in: body in: body
name: body name: body
required: true required: true
@@ -971,24 +971,43 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
summary: 提交系统信息 summary: submit system info
tags: tags:
- 地址 - System
/sysinfo_ver:
post:
consumes:
- application/json
description: get system version info
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: get system version info
tags:
- System
/users: /users:
get: get:
consumes: consumes:
- application/json - application/json
description: 用户列表 description: user list
parameters: parameters:
- description: 页码 - description: page number
in: query in: query
name: page name: page
type: integer type: integer
- description: 每页数量 - description: items per page
in: query in: query
name: pageSize name: pageSize
type: integer type: integer
- description: 状态 - description: status
in: query in: query
name: status name: status
type: integer type: integer
@@ -1016,14 +1035,14 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 用户列表 summary: user list
tags: tags:
- 群组 - group
/version: /version:
get: get:
consumes: consumes:
- application/json - application/json
description: 版本 description: version
produces: produces:
- application/json - application/json
responses: responses:
@@ -1035,9 +1054,9 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
summary: 版本 summary: version
tags: tags:
- 首页 - home
securityDefinitions: securityDefinitions:
BearerAuth: BearerAuth:
in: header in: header
+4 -4
View File
@@ -14,6 +14,7 @@ import (
en_translations "github.com/go-playground/validator/v10/translations/en" en_translations "github.com/go-playground/validator/v10/translations/en"
es_translations "github.com/go-playground/validator/v10/translations/es" es_translations "github.com/go-playground/validator/v10/translations/es"
fr_translations "github.com/go-playground/validator/v10/translations/fr" 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" ru_translations "github.com/go-playground/validator/v10/translations/ru"
zh_translations "github.com/go-playground/validator/v10/translations/zh" zh_translations "github.com/go-playground/validator/v10/translations/zh"
zh_tw_translations "github.com/go-playground/validator/v10/translations/zh_tw" zh_tw_translations "github.com/go-playground/validator/v10/translations/zh_tw"
@@ -23,7 +24,7 @@ import (
func ApiInitValidator() { func ApiInitValidator() {
validate := validator.New() validate := validator.New()
// 定义不同的语言翻译 // define translations for different languages
enT := en.New() enT := en.New()
cn := zh_Hans_CN.New() cn := zh_Hans_CN.New()
koT := ko.New() koT := ko.New()
@@ -51,8 +52,7 @@ func ApiInitValidator() {
panic(err) panic(err)
} }
//validate没有ko的翻译,使用zh的翻译 err = ko_translations.RegisterDefaultTranslations(validate, koTrans)
err = zh_translations.RegisterDefaultTranslations(validate, koTrans)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -81,7 +81,7 @@ func ApiInitValidator() {
return label return label
}) })
Validator.Validate = validate Validator.Validate = validate
Validator.UT = uni // 存储 Universal Translator Validator.UT = uni // store the Universal Translator
Validator.VTrans = zhTrans Validator.VTrans = zhTrans
Validator.ValidStruct = func(ctx *gin.Context, i interface{}) []string { Validator.ValidStruct = func(ctx *gin.Context, i interface{}) []string {
+6 -4
View File
@@ -10,6 +10,7 @@ import (
"github.com/lejianwen/rustdesk-api/v2/lib/jwt" "github.com/lejianwen/rustdesk-api/v2/lib/jwt"
"github.com/lejianwen/rustdesk-api/v2/lib/lock" "github.com/lejianwen/rustdesk-api/v2/lib/lock"
"github.com/lejianwen/rustdesk-api/v2/lib/upload" "github.com/lejianwen/rustdesk-api/v2/lib/upload"
"github.com/lejianwen/rustdesk-api/v2/utils"
"github.com/nicksnyder/go-i18n/v2/i18n" "github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/viper" "github.com/spf13/viper"
@@ -31,8 +32,9 @@ var (
ValidStruct func(*gin.Context, interface{}) []string ValidStruct func(*gin.Context, interface{}) []string
ValidVar func(ctx *gin.Context, field interface{}, tag string) []string ValidVar func(ctx *gin.Context, field interface{}, tag string) []string
} }
Oss *upload.Oss Oss *upload.Oss
Jwt *jwt.Jwt Jwt *jwt.Jwt
Lock lock.Locker Lock lock.Locker
Localizer func(lang string) *i18n.Localizer Localizer func(lang string) *i18n.Localizer
LoginLimiter *utils.LoginLimiter
) )
+2 -2
View File
@@ -10,14 +10,14 @@ import (
func InitI18n() { func InitI18n() {
bundle := i18n.NewBundle(language.English) bundle := i18n.NewBundle(language.English)
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
//读取global.Config.Gin.ResourcesPath下的所有语言文件 // read all language files under global.Config.Gin.ResourcesPath
dir := Config.Gin.ResourcesPath + "/i18n" dir := Config.Gin.ResourcesPath + "/i18n"
fileInfos, err := os.ReadDir(dir) fileInfos, err := os.ReadDir(dir)
if err != nil { if err != nil {
panic(err) panic(err)
} }
for _, fileInfo := range fileInfos { for _, fileInfo := range fileInfos {
//如果文件名不是.toml结尾 // if the file name does not end with .toml
if fileInfo.IsDir() || fileInfo.Name()[len(fileInfo.Name())-5:] != ".toml" { if fileInfo.IsDir() || fileInfo.Name()[len(fileInfo.Name())-5:] != ".toml" {
continue continue
} }
+23 -13
View File
@@ -1,19 +1,23 @@
module github.com/lejianwen/rustdesk-api/v2 module github.com/lejianwen/rustdesk-api/v2
go 1.22 go 1.23
toolchain go1.23.10
require ( require (
github.com/BurntSushi/toml v1.3.2 github.com/BurntSushi/toml v1.3.2
github.com/antonfisher/nested-logrus-formatter v1.3.1 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/fvbock/endless v0.0.0-20170109170031-447134032cb6
github.com/gin-gonic/gin v1.9.0 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/locales v0.14.1
github.com/go-playground/universal-translator v0.18.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/go-redis/redis/v8 v8.11.4
github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0 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/nicksnyder/go-i18n/v2 v2.4.0
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1
@@ -21,11 +25,13 @@ require (
github.com/swaggo/files v1.0.1 github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.3 github.com/swaggo/swag v1.16.3
golang.org/x/crypto v0.33.0
golang.org/x/oauth2 v0.23.0 golang.org/x/oauth2 v0.23.0
golang.org/x/text v0.21.0 golang.org/x/text v0.22.0
gorm.io/driver/mysql v1.5.7 gorm.io/driver/mysql v1.5.7
gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlite v1.5.6 gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.7 gorm.io/gorm v1.25.10
) )
require ( require (
@@ -36,12 +42,12 @@ require (
github.com/bytedance/sonic v1.8.0 // indirect github.com/bytedance/sonic v1.8.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/coreos/go-oidc/v3 v3.12.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // 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/gin-contrib/sse v0.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // 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-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-ldap/ldap/v3 v3.4.10 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect github.com/go-openapi/spec v0.20.4 // indirect
@@ -51,12 +57,16 @@ require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.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/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // 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/magiconair/properties v1.8.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.17 // indirect
@@ -64,9 +74,9 @@ require (
github.com/mitchellh/mapstructure v1.4.2 // indirect github.com/mitchellh/mapstructure v1.4.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mojocn/base64Captcha v1.3.6 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // 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/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
@@ -75,11 +85,11 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/image v0.13.0 // indirect golang.org/x/image v0.13.0 // indirect
golang.org/x/net v0.33.0 // indirect golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.28.0 // indirect golang.org/x/sync v0.11.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // 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 google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect gopkg.in/ini.v1 v1.63.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
+39 -39
View File
@@ -15,10 +15,10 @@ import (
type AddressBook struct { type AddressBook struct {
} }
// Detail 地址簿 // Detail address book
// @Tags 地址簿 // @Tags address book
// @Summary 地址簿详情 // @Summary address book details
// @Description 地址簿详情 // @Description address book details
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -38,13 +38,13 @@ func (ct *AddressBook) Detail(c *gin.Context) {
return return
} }
// Create 创建地址簿 // Create create address book
// @Tags 地址簿 // @Tags address book
// @Summary 创建地址簿 // @Summary create address book
// @Description 创建地址簿 // @Description create address book
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.AddressBookForm true "地址簿信息" // @Param body body admin.AddressBookForm true "address book info"
// @Success 200 {object} response.Response{data=model.AddressBook} // @Success 200 {object} response.Response{data=model.AddressBook}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book/create [post] // @Router /admin/address_book/create [post]
@@ -84,13 +84,13 @@ func (ct *AddressBook) Create(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// BatchCreate 批量创建地址簿 // BatchCreate batch create address books
// @Tags 地址簿 // @Tags address book
// @Summary 批量创建地址簿 // @Summary batch create address books
// @Description 批量创建地址簿 // @Description batch create address books
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.AddressBookForm true "地址簿信息" // @Param body body admin.AddressBookForm true "address book info"
// @Success 200 {object} response.Response{data=model.AddressBook} // @Success 200 {object} response.Response{data=model.AddressBook}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book/batchCreate [post] // @Router /admin/address_book/batchCreate [post]
@@ -113,13 +113,13 @@ func (ct *AddressBook) BatchCreate(c *gin.Context) {
return return
} }
if ul > 1 { if ul > 1 {
//多用户置空标签 //clear tags in multi-user mode
f.Tags = []string{} f.Tags = []string{}
//多用户只能创建到默认地址簿 //in multi-user mode, can only create in the default address book
f.CollectionId = 0 f.CollectionId = 0
} }
//创建标签 //create tag
/*for _, fu := range f.UserIds { /*for _, fu := range f.UserIds {
if fu == 0 { if fu == 0 {
continue continue
@@ -148,16 +148,16 @@ func (ct *AddressBook) BatchCreate(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// List 列表 // List list
// @Tags 地址簿 // @Tags address book
// @Summary 地址簿列表 // @Summary address book list
// @Description 地址簿列表 // @Description address book list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Param user_id query int false "用户id" // @Param user_id query int false "userid"
// @Param is_my query int false "是否是我的" // @Param is_my query int false "whether it is mine"
// @Success 200 {object} response.Response{data=model.AddressBookList} // @Success 200 {object} response.Response{data=model.AddressBookList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book/list [get] // @Router /admin/address_book/list [get]
@@ -196,13 +196,13 @@ func (ct *AddressBook) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Update 编辑 // Update edit
// @Tags 地址簿 // @Tags address book
// @Summary 地址簿编辑 // @Summary edit address book
// @Description 地址簿编辑 // @Description edit address book
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.AddressBookForm true "地址簿信息" // @Param body body admin.AddressBookForm true "address book info"
// @Success 200 {object} response.Response{data=model.AddressBook} // @Success 200 {object} response.Response{data=model.AddressBook}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book/update [post] // @Router /admin/address_book/update [post]
@@ -240,13 +240,13 @@ func (ct *AddressBook) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete 删除 // Delete delete
// @Tags 地址簿 // @Tags address book
// @Summary 地址簿删除 // @Summary delete address book
// @Description 地址簿删除 // @Description delete address book
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.AddressBookForm true "地址簿信息" // @Param body body admin.AddressBookForm true "address book info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book/delete [post] // @Router /admin/address_book/delete [post]
@@ -277,12 +277,12 @@ func (ct *AddressBook) Delete(c *gin.Context) {
} }
// ShareByWebClient // ShareByWebClient
// @Tags 地址簿 // @Tags address book
// @Summary 地址簿分享 // @Summary share address book
// @Description 地址簿分享 // @Description share address book
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.ShareByWebClientForm true "地址簿信息" // @Param body body admin.ShareByWebClientForm true "address book info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book/share [post] // @Router /admin/address_book/share [post]
+27 -27
View File
@@ -14,10 +14,10 @@ import (
type AddressBookCollection struct { type AddressBookCollection struct {
} }
// Detail 地址簿名称 // Detail address book name
// @Tags 地址簿名称 // @Tags address book name
// @Summary 地址簿名称详情 // @Summary address book name details
// @Description 地址簿名称详情 // @Description address book name details
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -37,13 +37,13 @@ func (abc *AddressBookCollection) Detail(c *gin.Context) {
return return
} }
// Create 创建地址簿名称 // Create create address book name
// @Tags 地址簿名称 // @Tags address book name
// @Summary 创建地址簿名称 // @Summary create address book name
// @Description 创建地址簿名称 // @Description create address book name
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息" // @Param body body model.AddressBookCollection true "address book name info"
// @Success 200 {object} response.Response{data=model.AddressBookCollection} // @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/create [post] // @Router /admin/address_book_collection/create [post]
@@ -72,16 +72,16 @@ func (abc *AddressBookCollection) Create(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// List 列表 // List list
// @Tags 地址簿名称 // @Tags address book name
// @Summary 地址簿名称列表 // @Summary address book name list
// @Description 地址簿名称列表 // @Description address book name list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Param is_my query int false "是否是我的" // @Param is_my query int false "whether it is mine"
// @Param user_id query int false "用户id" // @Param user_id query int false "userid"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList} // @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/list [get] // @Router /admin/address_book_collection/list [get]
@@ -100,13 +100,13 @@ func (abc *AddressBookCollection) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Update 编辑 // Update edit
// @Tags 地址簿名称 // @Tags address book name
// @Summary 地址簿名称编辑 // @Summary edit address book name
// @Description 地址簿名称编辑 // @Description edit address book name
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息" // @Param body body model.AddressBookCollection true "address book name info"
// @Success 200 {object} response.Response{data=model.AddressBookCollection} // @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/update [post] // @Router /admin/address_book_collection/update [post]
@@ -135,13 +135,13 @@ func (abc *AddressBookCollection) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete 删除 // Delete delete
// @Tags 地址簿名称 // @Tags address book name
// @Summary 地址簿名称删除 // @Summary delete address book name
// @Description 地址簿名称删除 // @Description delete address book name
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息" // @Param body body model.AddressBookCollection true "address book name info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/delete [post] // @Router /admin/address_book_collection/delete [post]
@@ -14,17 +14,17 @@ import (
type AddressBookCollectionRule struct { type AddressBookCollectionRule struct {
} }
// List 列表 // List list
// @Tags 地址簿规则 // @Tags address book rules
// @Summary 地址簿规则列表 // @Summary address book rule list
// @Description 地址簿规则列表 // @Description address book rule list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Param is_my query int false "是否是我的" // @Param is_my query int false "whether it is mine"
// @Param user_id query int false "用户id" // @Param user_id query int false "userid"
// @Param collection_id query int false "地址簿集合id" // @Param collection_id query int false "address book collectionid"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList} // @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/list [get] // @Router /admin/address_book_collection_rule/list [get]
@@ -47,10 +47,10 @@ func (abcr *AddressBookCollectionRule) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Detail 地址簿规则 // Detail address book rules
// @Tags 地址簿规则 // @Tags address book rules
// @Summary 地址簿规则详情 // @Summary address book rule details
// @Description 地址簿规则详情 // @Description address book rule details
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -69,13 +69,13 @@ func (abcr *AddressBookCollectionRule) Detail(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
} }
// Create 创建地址簿规则 // Create create address book rule
// @Tags 地址簿规则 // @Tags address book rules
// @Summary 创建地址簿规则 // @Summary create address book rule
// @Description 创建地址簿规则 // @Description create address book rule
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息" // @Param body body model.AddressBookCollectionRule true "address book rule info"
// @Success 200 {object} response.Response{data=model.AddressBookCollection} // @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/create [post] // @Router /admin/address_book_collection_rule/create [post]
@@ -120,7 +120,7 @@ func (abcr *AddressBookCollectionRule) CheckForm(t *model.AddressBookCollectionR
//check to_id //check to_id
if t.Type == model.ShareAddressBookRuleTypePersonal { if t.Type == model.ShareAddressBookRuleTypePersonal {
if t.ToId == t.UserId { if t.ToId == t.UserId {
return "ParamsError", false return "CannotShareToSelf", false
} }
tou := service.AllService.UserService.InfoById(t.ToId) tou := service.AllService.UserService.InfoById(t.ToId)
if tou.Id == 0 { if tou.Id == 0 {
@@ -134,8 +134,8 @@ func (abcr *AddressBookCollectionRule) CheckForm(t *model.AddressBookCollectionR
} else { } else {
return "ParamsError", false return "ParamsError", false
} }
// 重复检查 // duplicate check
ex := service.AllService.AddressBookService.RulePersonalInfoByToIdAndCid(t.ToId, t.CollectionId) ex := service.AllService.AddressBookService.RuleInfoByToIdAndCid(t.Type, t.ToId, t.CollectionId)
if t.Id == 0 && ex.Id > 0 { if t.Id == 0 && ex.Id > 0 {
return "ItemExists", false return "ItemExists", false
} }
@@ -145,13 +145,13 @@ func (abcr *AddressBookCollectionRule) CheckForm(t *model.AddressBookCollectionR
return "", true return "", true
} }
// Update 编辑 // Update edit
// @Tags 地址簿规则 // @Tags address book rules
// @Summary 地址簿规则编辑 // @Summary edit address book rule
// @Description 地址簿规则编辑 // @Description edit address book rule
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息" // @Param body body model.AddressBookCollectionRule true "address book rule info"
// @Success 200 {object} response.Response{data=model.AddressBookCollection} // @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/update [post] // @Router /admin/address_book_collection_rule/update [post]
@@ -185,13 +185,13 @@ func (abcr *AddressBookCollectionRule) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete 删除 // Delete delete
// @Tags 地址簿规则 // @Tags address book rules
// @Summary 地址簿规则删除 // @Summary delete address book rule
// @Description 地址簿规则删除 // @Description delete address book rule
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息" // @Param body body model.AddressBookCollectionRule true "address book rule info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/delete [post] // @Router /admin/address_book_collection_rule/delete [post]
+36 -36
View File
@@ -13,16 +13,16 @@ import (
type Audit struct { type Audit struct {
} }
// ConnList 列表 // ConnList list
// @Tags 链接日志 // @Tags connection log
// @Summary 链接日志列表 // @Summary connection log list
// @Description 链接日志列表 // @Description connection log list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Param peer_id query int false "目标设备" // @Param peer_id query int false "target device"
// @Param from_peer query int false "来源设备" // @Param from_peer query int false "source device"
// @Success 200 {object} response.Response{data=model.AuditConnList} // @Success 200 {object} response.Response{data=model.AuditConnList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/audit_conn/list [get] // @Router /admin/audit_conn/list [get]
@@ -45,13 +45,13 @@ func (a *Audit) ConnList(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// ConnDelete 删除 // ConnDelete delete
// @Tags 链接日志 // @Tags connection log
// @Summary 链接日志删除 // @Summary delete connection log
// @Description 链接日志删除 // @Description delete connection log
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.AuditConn true "链接日志信息" // @Param body body model.AuditConn true "connection log info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/audit_conn/delete [post] // @Router /admin/audit_conn/delete [post]
@@ -81,13 +81,13 @@ func (a *Audit) ConnDelete(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
} }
// BatchConnDelete 删除 // BatchConnDelete delete
// @Tags 链接日志 // @Tags connection log
// @Summary 链接日志批量删除 // @Summary batch delete connection logs
// @Description 链接日志批量删除 // @Description batch delete connection logs
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.AuditConnLogIds true "链接日志" // @Param body body admin.AuditConnLogIds true "connection log"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/audit_conn/batchDelete [post] // @Router /admin/audit_conn/batchDelete [post]
@@ -112,16 +112,16 @@ func (a *Audit) BatchConnDelete(c *gin.Context) {
return return
} }
// FileList 列表 // FileList list
// @Tags 文件日志 // @Tags file log
// @Summary 文件日志列表 // @Summary file log list
// @Description 文件日志列表 // @Description file log list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Param peer_id query int false "目标设备" // @Param peer_id query int false "target device"
// @Param from_peer query int false "来源设备" // @Param from_peer query int false "source device"
// @Success 200 {object} response.Response{data=model.AuditFileList} // @Success 200 {object} response.Response{data=model.AuditFileList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/audit_file/list [get] // @Router /admin/audit_file/list [get]
@@ -144,13 +144,13 @@ func (a *Audit) FileList(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// FileDelete 删除 // FileDelete delete
// @Tags 文件日志 // @Tags file log
// @Summary 文件日志删除 // @Summary delete file log
// @Description 文件日志删除 // @Description delete file log
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.AuditFile true "文件日志信息" // @Param body body model.AuditFile true "file log info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/audit_file/delete [post] // @Router /admin/audit_file/delete [post]
@@ -180,13 +180,13 @@ func (a *Audit) FileDelete(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
} }
// BatchFileDelete 删除 // BatchFileDelete delete
// @Tags 文件日志 // @Tags file log
// @Summary 文件日志批量删除 // @Summary batch delete file logs
// @Description 文件日志批量删除 // @Description batch delete file logs
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.AuditFileLogIds true "文件日志" // @Param body body admin.AuditFileLogIds true "file log"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/audit_file/batchDelete [post] // @Router /admin/audit_file/batchDelete [post]
+16 -14
View File
@@ -13,10 +13,10 @@ import (
type Config struct { type Config struct {
} }
// ServerConfig RUSTDESK服务配置 // ServerConfig RUSTDESKservice config
// @Tags ADMIN // @Tags ADMIN
// @Summary RUSTDESK服务配置 // @Summary RUSTDESKservice config
// @Description 服务配置,给webclient提供api-server // @Description service config, provides an api-server for the webclient
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
@@ -33,10 +33,10 @@ func (co *Config) ServerConfig(c *gin.Context) {
response.Success(c, cf) response.Success(c, cf)
} }
// AppConfig APP服务配置 // AppConfig APPservice config
// @Tags ADMIN // @Tags ADMIN
// @Summary APP服务配置 // @Summary APPservice config
// @Description APP服务配置 // @Description APPservice config
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
@@ -49,10 +49,10 @@ func (co *Config) AppConfig(c *gin.Context) {
}) })
} }
// AdminConfig ADMIN服务配置 // AdminConfig ADMINservice config
// @Tags ADMIN // @Tags ADMIN
// @Summary ADMIN服务配置 // @Summary ADMINservice config
// @Description ADMIN服务配置 // @Description ADMINservice config
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
@@ -78,11 +78,13 @@ func (co *Config) AdminConfig(c *gin.Context) {
} }
hello := global.Config.Admin.Hello hello := global.Config.Admin.Hello
helloFile := global.Config.Admin.HelloFile if hello == "" {
if helloFile != "" { helloFile := global.Config.Admin.HelloFile
b, err := os.ReadFile(helloFile) if helloFile != "" {
if err == nil && len(b) > 0 { b, err := os.ReadFile(helloFile)
hello = string(b) if err == nil && len(b) > 0 {
hello = string(b)
}
} }
} }
+25 -25
View File
@@ -12,10 +12,10 @@ import (
type DeviceGroup struct { type DeviceGroup struct {
} }
// Detail 设备群组 // Detail device group
// @Tags 设备群组 // @Tags device group
// @Summary 设备群组详情 // @Summary device group details
// @Description 设备群组详情 // @Description device group details
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -35,13 +35,13 @@ func (ct *DeviceGroup) Detail(c *gin.Context) {
return return
} }
// Create 创建设备群组 // Create create device group
// @Tags 设备群组 // @Tags device group
// @Summary 创建设备群组 // @Summary create device group
// @Description 创建设备群组 // @Description create device group
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.DeviceGroupForm true "设备群组信息" // @Param body body admin.DeviceGroupForm true "device group info"
// @Success 200 {object} response.Response{data=model.DeviceGroup} // @Success 200 {object} response.Response{data=model.DeviceGroup}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/device_group/create [post] // @Router /admin/device_group/create [post]
@@ -66,14 +66,14 @@ func (ct *DeviceGroup) Create(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// List 列表 // List list
// @Tags 群组 // @Tags group
// @Summary 群组列表 // @Summary group list
// @Description 群组列表 // @Description group list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Success 200 {object} response.Response{data=model.GroupList} // @Success 200 {object} response.Response{data=model.GroupList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/device_group/list [get] // @Router /admin/device_group/list [get]
@@ -88,13 +88,13 @@ func (ct *DeviceGroup) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Update 编辑 // Update edit
// @Tags 设备群组 // @Tags device group
// @Summary 设备群组编辑 // @Summary edit device group
// @Description 设备群组编辑 // @Description edit device group
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.DeviceGroupForm true "群组信息" // @Param body body admin.DeviceGroupForm true "group info"
// @Success 200 {object} response.Response{data=model.Group} // @Success 200 {object} response.Response{data=model.Group}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/device_group/update [post] // @Router /admin/device_group/update [post]
@@ -123,13 +123,13 @@ func (ct *DeviceGroup) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete 删除 // Delete delete
// @Tags 设备群组 // @Tags device group
// @Summary 设备群组删除 // @Summary delete device group
// @Description 设备群组删除 // @Description delete device group
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.DeviceGroupForm true "群组信息" // @Param body body admin.DeviceGroupForm true "group info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/device_group/delete [post] // @Router /admin/device_group/delete [post]
+13 -13
View File
@@ -13,10 +13,10 @@ import (
type File struct { type File struct {
} }
// OssToken 文件 // OssToken file
// @Tags 文件 // @Tags file
// @Summary 获取ossToken // @Summary getossToken
// @Description 获取ossToken // @Description getossToken
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
@@ -33,12 +33,12 @@ type FileBack struct {
Url string `json:"url"` Url string `json:"url"`
} }
// Notify 上传成功后回调 // Notify callback after successful upload
func (f *File) Notify(c *gin.Context) { func (f *File) Notify(c *gin.Context) {
res := global.Oss.Verify(c.Request) res := global.Oss.Verify(c.Request)
if !res { if !res {
response.Fail(c, 101, "权限错误") response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return return
} }
fm := &FileBack{} fm := &FileBack{}
@@ -50,13 +50,13 @@ func (f *File) Notify(c *gin.Context) {
} }
// Upload 上传文件到本地 // Upload upload file to local
// @Tags 文件 // @Tags file
// @Summary 上传文件到本地 // @Summary upload file to local
// @Description 上传文件到本地 // @Description upload file to local
// @Accept multipart/form-data // @Accept multipart/form-data
// @Produce json // @Produce json
// @Param file formData file true "上传文件示例" // @Param file formData file true "file upload example"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/file/upload [post] // @Router /admin/file/upload [post]
@@ -71,12 +71,12 @@ func (f *File) Upload(c *gin.Context) {
if err != nil { if err != nil {
return return
} }
// 上传文件至指定目录 // upload the file to the specified directory
err = c.SaveUploadedFile(file, dst) err = c.SaveUploadedFile(file, dst)
if err != nil { if err != nil {
return return
} }
// 返回文件web地址 // return filewebaddress
response.Success(c, gin.H{ response.Success(c, gin.H{
"url": webPath + file.Filename, "url": webPath + file.Filename,
}) })
+25 -25
View File
@@ -12,10 +12,10 @@ import (
type Group struct { type Group struct {
} }
// Detail 群组 // Detail group
// @Tags 群组 // @Tags group
// @Summary 群组详情 // @Summary group details
// @Description 群组详情 // @Description group details
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -35,13 +35,13 @@ func (ct *Group) Detail(c *gin.Context) {
return return
} }
// Create 创建群组 // Create create group
// @Tags 群组 // @Tags group
// @Summary 创建群组 // @Summary create group
// @Description 创建群组 // @Description create group
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.GroupForm true "群组信息" // @Param body body admin.GroupForm true "group info"
// @Success 200 {object} response.Response{data=model.Group} // @Success 200 {object} response.Response{data=model.Group}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/group/create [post] // @Router /admin/group/create [post]
@@ -66,14 +66,14 @@ func (ct *Group) Create(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// List 列表 // List list
// @Tags 群组 // @Tags group
// @Summary 群组列表 // @Summary group list
// @Description 群组列表 // @Description group list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Success 200 {object} response.Response{data=model.GroupList} // @Success 200 {object} response.Response{data=model.GroupList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/group/list [get] // @Router /admin/group/list [get]
@@ -88,13 +88,13 @@ func (ct *Group) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Update 编辑 // Update edit
// @Tags 群组 // @Tags group
// @Summary 群组编辑 // @Summary edit group
// @Description 群组编辑 // @Description edit group
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.GroupForm true "群组信息" // @Param body body admin.GroupForm true "group info"
// @Success 200 {object} response.Response{data=model.Group} // @Success 200 {object} response.Response{data=model.Group}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/group/update [post] // @Router /admin/group/update [post]
@@ -123,13 +123,13 @@ func (ct *Group) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete 删除 // Delete delete
// @Tags 群组 // @Tags group
// @Summary 群组删除 // @Summary delete group
// @Description 群组删除 // @Description delete group
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.GroupForm true "群组信息" // @Param body body admin.GroupForm true "group info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/group/delete [post] // @Router /admin/group/delete [post]
+65 -157
View File
@@ -2,6 +2,7 @@ package admin
import ( import (
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global" "github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/controller/api" "github.com/lejianwen/rustdesk-api/v2/http/controller/api"
@@ -11,142 +12,18 @@ import (
adResp "github.com/lejianwen/rustdesk-api/v2/http/response/admin" adResp "github.com/lejianwen/rustdesk-api/v2/http/response/admin"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service" "github.com/lejianwen/rustdesk-api/v2/service"
"github.com/mojocn/base64Captcha"
"sync"
"time"
) )
type Login struct { type Login struct {
} }
// Captcha 验证码结构 // Login login
type Captcha struct { // @Tags login
Id string `json:"id"` // 验证码 ID // @Summary login
B64 string `json:"b64"` // base64 验证码 // @Description login
Code string `json:"-"` // 验证码内容
ExpiresAt time.Time `json:"-"` // 过期时间
}
type LoginLimiter struct {
mu sync.RWMutex
failCount map[string]int // 记录每个 IP 的失败次数
timestamp map[string]time.Time // 记录每个 IP 的最后失败时间
captchas map[string]Captcha // 每个 IP 的验证码
threshold int // 失败阈值
expiry time.Duration // 失败记录过期时间
}
func NewLoginLimiter(threshold int, expiry time.Duration) *LoginLimiter {
return &LoginLimiter{
failCount: make(map[string]int),
timestamp: make(map[string]time.Time),
captchas: make(map[string]Captcha),
threshold: threshold,
expiry: expiry,
}
}
// RecordFailure 记录登录失败
func (l *LoginLimiter) RecordFailure(ip string) {
l.mu.Lock()
defer l.mu.Unlock()
// 如果该 IP 的记录已经过期,重置计数
if lastTime, exists := l.timestamp[ip]; exists && time.Since(lastTime) > l.expiry {
l.failCount[ip] = 0
}
// 更新失败次数和时间戳
l.failCount[ip]++
l.timestamp[ip] = time.Now()
}
// NeedsCaptcha 检查是否需要验证码
func (l *LoginLimiter) NeedsCaptcha(ip string) bool {
l.mu.RLock()
defer l.mu.RUnlock()
// 检查记录是否存在且未过期
if lastTime, exists := l.timestamp[ip]; exists && time.Since(lastTime) <= l.expiry {
return l.failCount[ip] >= l.threshold
}
return false
}
// GenerateCaptcha 为指定 IP 生成验证码
func (l *LoginLimiter) GenerateCaptcha(ip string) Captcha {
l.mu.Lock()
defer l.mu.Unlock()
capd := base64Captcha.NewDriverString(50, 150, 5, 10, 4, "1234567890abcdefghijklmnopqrstuvwxyz", nil, nil, nil)
b64cap := base64Captcha.NewCaptcha(capd, base64Captcha.DefaultMemStore)
id, b64s, answer, err := b64cap.Generate()
if err != nil {
global.Logger.Error("Generate captcha failed: " + err.Error())
return Captcha{}
}
// 保存验证码到对应 IP
l.captchas[ip] = Captcha{
Id: id,
B64: b64s,
Code: answer,
ExpiresAt: time.Now().Add(5 * time.Minute),
}
return l.captchas[ip]
}
// VerifyCaptcha 验证指定 IP 的验证码
func (l *LoginLimiter) VerifyCaptcha(ip, code string) bool {
l.mu.RLock()
defer l.mu.RUnlock()
// 检查验证码是否存在且未过期
if captcha, exists := l.captchas[ip]; exists && time.Now().Before(captcha.ExpiresAt) {
return captcha.Code == code
}
return false
}
// RemoveCaptcha 移除指定 IP 的验证码
func (l *LoginLimiter) RemoveCaptcha(ip string) {
l.mu.Lock()
defer l.mu.Unlock()
delete(l.captchas, ip)
}
// CleanupExpired 清理过期的记录
func (l *LoginLimiter) CleanupExpired() {
l.mu.Lock()
defer l.mu.Unlock()
now := time.Now()
for ip, lastTime := range l.timestamp {
if now.Sub(lastTime) > l.expiry {
delete(l.failCount, ip)
delete(l.timestamp, ip)
delete(l.captchas, ip)
}
}
}
func (l *LoginLimiter) RemoveRecord(ip string) {
l.mu.Lock()
defer l.mu.Unlock()
delete(l.failCount, ip)
delete(l.timestamp, ip)
delete(l.captchas, ip)
}
var loginLimiter = NewLoginLimiter(3, 5*time.Minute)
// Login 登录
// @Tags 登录
// @Summary 登录
// @Description 登录
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.Login true "登录信息" // @Param body body admin.Login true "login info"
// @Success 200 {object} response.Response{data=adResp.LoginPayload} // @Success 200 {object} response.Response{data=adResp.LoginPayload}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/login [post] // @Router /admin/login [post]
@@ -156,10 +33,16 @@ func (ct *Login) Login(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "PwdLoginDisabled")) response.Fail(c, 101, response.TranslateMsg(c, "PwdLoginDisabled"))
return return
} }
// check login limit
loginLimiter := global.LoginLimiter
clientIp := c.ClientIP()
_, needCaptcha := loginLimiter.CheckSecurityStatus(clientIp)
f := &admin.Login{} f := &admin.Login{}
err := c.ShouldBindJSON(f) err := c.ShouldBindJSON(f)
clientIp := c.ClientIP()
if err != nil { if err != nil {
loginLimiter.RecordFailedAttempt(clientIp)
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), 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()) response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return return
@@ -167,14 +50,15 @@ func (ct *Login) Login(c *gin.Context) {
errList := global.Validator.ValidStruct(c, f) errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 { if len(errList) > 0 {
loginLimiter.RecordFailedAttempt(clientIp)
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), clientIp)) global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), clientIp))
response.Fail(c, 101, errList[0]) response.Fail(c, 101, errList[0])
return return
} }
// 检查是否需要验证码 // check whether a captcha is required
if loginLimiter.NeedsCaptcha(clientIp) { if needCaptcha {
if f.Captcha == "" || !loginLimiter.VerifyCaptcha(clientIp, f.Captcha) { if f.CaptchaId == "" || f.Captcha == "" || !loginLimiter.VerifyCaptcha(f.CaptchaId, f.Captcha) {
response.Fail(c, 101, response.TranslateMsg(c, "CaptchaError")) response.Fail(c, 101, response.TranslateMsg(c, "CaptchaError"))
return return
} }
@@ -184,17 +68,19 @@ func (ct *Login) Login(c *gin.Context) {
if u.Id == 0 { if u.Id == 0 {
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "UsernameOrPasswordError", c.RemoteIP(), clientIp)) global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "UsernameOrPasswordError", c.RemoteIP(), clientIp))
loginLimiter.RecordFailure(clientIp) loginLimiter.RecordFailedAttempt(clientIp)
if loginLimiter.NeedsCaptcha(clientIp) { if _, needCaptcha = loginLimiter.CheckSecurityStatus(clientIp); needCaptcha {
loginLimiter.RemoveCaptcha(clientIp) response.Fail(c, 110, response.TranslateMsg(c, "UsernameOrPasswordError"))
} else {
response.Fail(c, 101, response.TranslateMsg(c, "UsernameOrPasswordError"))
} }
response.Fail(c, 101, response.TranslateMsg(c, "UsernameOrPasswordError"))
return return
} }
if !service.AllService.UserService.CheckUserEnable(u) { if !service.AllService.UserService.CheckUserEnable(u) {
if loginLimiter.NeedsCaptcha(clientIp) { if needCaptcha {
loginLimiter.RemoveCaptcha(clientIp) response.Fail(c, 110, response.TranslateMsg(c, "UserDisabled"))
return
} }
response.Fail(c, 101, response.TranslateMsg(c, "UserDisabled")) response.Fail(c, 101, response.TranslateMsg(c, "UserDisabled"))
return return
@@ -209,30 +95,44 @@ func (ct *Login) Login(c *gin.Context) {
Platform: f.Platform, Platform: f.Platform,
}) })
// 成功后清除记录 // login successful, clear the login limit
loginLimiter.RemoveRecord(clientIp) loginLimiter.RemoveAttempts(clientIp)
// 清理过期记录
go loginLimiter.CleanupExpired()
responseLoginSuccess(c, u, ut.Token) responseLoginSuccess(c, u, ut.Token)
} }
func (ct *Login) Captcha(c *gin.Context) { func (ct *Login) Captcha(c *gin.Context) {
loginLimiter := global.LoginLimiter
clientIp := c.ClientIP() clientIp := c.ClientIP()
if !loginLimiter.NeedsCaptcha(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")) response.Fail(c, 101, response.TranslateMsg(c, "NoCaptchaRequired"))
return return
} }
captcha := loginLimiter.GenerateCaptcha(clientIp) 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{ response.Success(c, gin.H{
"captcha": captcha, "captcha": gin.H{
"id": captcha.Id,
"b64": b64,
},
}) })
} }
// Logout 登出 // Logout logout
// @Tags 登录 // @Tags login
// @Summary 登出 // @Summary logout
// @Description 登出 // @Description logout
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
@@ -248,21 +148,29 @@ func (ct *Login) Logout(c *gin.Context) {
} }
// LoginOptions // LoginOptions
// @Tags 登录 // @Tags login
// @Summary 登录选项 // @Summary login options
// @Description 登录选项 // @Description login options
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} []string // @Success 200 {object} []string
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /admin/login-options [post] // @Router /admin/login-options [post]
func (ct *Login) LoginOptions(c *gin.Context) { func (ct *Login) LoginOptions(c *gin.Context) {
ip := c.ClientIP() 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() ops := service.AllService.OauthService.GetOauthProviders()
response.Success(c, gin.H{ response.Success(c, gin.H{
"ops": ops, "ops": ops,
"register": global.Config.App.Register, "register": global.Config.App.Register,
"need_captcha": loginLimiter.NeedsCaptcha(ip), "need_captcha": needCaptcha,
"disable_pwd": global.Config.App.DisablePwdLogin,
"auto_oidc": global.Config.App.DisablePwdLogin && len(ops) == 1,
}) })
} }
+21 -21
View File
@@ -14,10 +14,10 @@ import (
type LoginLog struct { type LoginLog struct {
} }
// Detail 登录日志 // Detail login log
// @Tags 登录日志 // @Tags login log
// @Summary 登录日志详情 // @Summary login log details
// @Description 登录日志详情 // @Description login log details
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -37,15 +37,15 @@ func (ct *LoginLog) Detail(c *gin.Context) {
return return
} }
// List 列表 // List list
// @Tags 登录日志 // @Tags login log
// @Summary 登录日志列表 // @Summary login log list
// @Description 登录日志列表 // @Description login log list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Param user_id query int false "用户ID" // @Param user_id query int false "userID"
// @Success 200 {object} response.Response{data=model.LoginLogList} // @Success 200 {object} response.Response{data=model.LoginLogList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/login_log/list [get] // @Router /admin/login_log/list [get]
@@ -65,13 +65,13 @@ func (ct *LoginLog) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Delete 删除 // Delete delete
// @Tags 登录日志 // @Tags login log
// @Summary 登录日志删除 // @Summary delete login log
// @Description 登录日志删除 // @Description delete login log
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.LoginLog true "登录日志信息" // @Param body body model.LoginLog true "login log info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/login_log/delete [post] // @Router /admin/login_log/delete [post]
@@ -101,13 +101,13 @@ func (ct *LoginLog) Delete(c *gin.Context) {
response.Fail(c, 101, err.Error()) response.Fail(c, 101, err.Error())
} }
// BatchDelete 删除 // BatchDelete delete
// @Tags 登录日志 // @Tags login log
// @Summary 登录日志批量删除 // @Summary batch delete login logs
// @Description 登录日志批量删除 // @Description batch delete login logs
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.LoginLogIds true "登录日志" // @Param body body admin.LoginLogIds true "login log"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/login_log/batchDelete [post] // @Router /admin/login_log/batchDelete [post]
+23 -23
View File
@@ -12,15 +12,15 @@ import (
type AddressBook struct{} type AddressBook struct{}
// List 列表 // List list
// @Tags 我的地址簿 // @Tags my address book
// @Summary 地址簿列表 // @Summary address book list
// @Description 地址簿列表 // @Description address book list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Param user_id query int false "用户id" // @Param user_id query int false "userid"
// @Success 200 {object} response.Response{data=model.AddressBookList} // @Success 200 {object} response.Response{data=model.AddressBookList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/address_book/list [get] // @Router /admin/my/address_book/list [get]
@@ -34,7 +34,7 @@ func (ct *AddressBook) List(c *gin.Context) {
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
query.UserId = int(u.Id) query.UserId = int(u.Id)
res := service.AllService.AddressBookService.List(query.Page, query.PageSize, func(tx *gorm.DB) { res := service.AllService.AddressBookService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
//预加载地址簿名称 //preload address book name
tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB { tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB {
return txc.Select("id,name") return txc.Select("id,name")
}) })
@@ -60,13 +60,13 @@ func (ct *AddressBook) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Create 创建地址簿 // Create create address book
// @Tags 我的地址簿 // @Tags my address book
// @Summary 创建地址簿 // @Summary create address book
// @Description 创建地址簿 // @Description create address book
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.AddressBookForm true "地址簿信息" // @Param body body admin.AddressBookForm true "address book info"
// @Success 200 {object} response.Response{data=model.AddressBook} // @Success 200 {object} response.Response{data=model.AddressBook}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/address_book/create [post] // @Router /admin/my/address_book/create [post]
@@ -104,13 +104,13 @@ func (ct *AddressBook) Create(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Update 编辑 // Update edit
// @Tags 我的地址簿 // @Tags my address book
// @Summary 地址簿编辑 // @Summary edit address book
// @Description 地址簿编辑 // @Description edit address book
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.AddressBookForm true "地址簿信息" // @Param body body admin.AddressBookForm true "address book info"
// @Success 200 {object} response.Response{data=model.AddressBook} // @Success 200 {object} response.Response{data=model.AddressBook}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/address_book/update [post] // @Router /admin/my/address_book/update [post]
@@ -158,13 +158,13 @@ func (ct *AddressBook) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete 删除 // Delete delete
// @Tags 我的地址簿 // @Tags my address book
// @Summary 地址簿删除 // @Summary delete address book
// @Description 地址簿删除 // @Description delete address book
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.AddressBookForm true "地址簿信息" // @Param body body admin.AddressBookForm true "address book info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/address_book/delete [post] // @Router /admin/my/address_book/delete [post]
@@ -13,13 +13,13 @@ import (
type AddressBookCollection struct { type AddressBookCollection struct {
} }
// Create 创建地址簿名称 // Create create address book name
// @Tags 我的地址簿名称 // @Tags my address book name
// @Summary 创建地址簿名称 // @Summary create address book name
// @Description 创建地址簿名称 // @Description create address book name
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息" // @Param body body model.AddressBookCollection true "address book name info"
// @Success 200 {object} response.Response{data=model.AddressBookCollection} // @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection/create [post] // @Router /admin/my/address_book_collection/create [post]
@@ -45,14 +45,14 @@ func (abc *AddressBookCollection) Create(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// List 列表 // List list
// @Tags 我的地址簿名称 // @Tags my address book name
// @Summary 地址簿名称列表 // @Summary address book name list
// @Description 地址簿名称列表 // @Description address book name list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList} // @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection/list [get] // @Router /admin/my/address_book_collection/list [get]
@@ -71,13 +71,13 @@ func (abc *AddressBookCollection) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Update 编辑 // Update edit
// @Tags 我的地址簿名称 // @Tags my address book name
// @Summary 地址簿名称编辑 // @Summary edit address book name
// @Description 地址簿名称编辑 // @Description edit address book name
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息" // @Param body body model.AddressBookCollection true "address book name info"
// @Success 200 {object} response.Response{data=model.AddressBookCollection} // @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection/update [post] // @Router /admin/my/address_book_collection/update [post]
@@ -98,10 +98,10 @@ func (abc *AddressBookCollection) Update(c *gin.Context) {
return return
} }
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
if f.UserId != u.Id { //if f.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess")) // response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return // return
} //}
ex := service.AllService.AddressBookService.CollectionInfoById(f.Id) ex := service.AllService.AddressBookService.CollectionInfoById(f.Id)
if ex.Id == 0 { if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
@@ -120,13 +120,13 @@ func (abc *AddressBookCollection) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete 删除 // Delete delete
// @Tags 我的地址簿名称 // @Tags my address book name
// @Summary 地址簿名称删除 // @Summary delete address book name
// @Description 地址簿名称删除 // @Description delete address book name
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息" // @Param body body model.AddressBookCollection true "address book name info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection/delete [post] // @Router /admin/my/address_book_collection/delete [post]
@@ -13,17 +13,17 @@ import (
type AddressBookCollectionRule struct { type AddressBookCollectionRule struct {
} }
// List 列表 // List list
// @Tags 我的地址簿规则 // @Tags my address book rules
// @Summary 地址簿规则列表 // @Summary address book rule list
// @Description 地址簿规则列表 // @Description address book rule list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Param is_my query int false "是否是我的" // @Param is_my query int false "whether it is mine"
// @Param user_id query int false "用户id" // @Param user_id query int false "userid"
// @Param collection_id query int false "地址簿集合id" // @Param collection_id query int false "address book collectionid"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList} // @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection_rule/list [get] // @Router /admin/my/address_book_collection_rule/list [get]
@@ -46,13 +46,13 @@ func (abcr *AddressBookCollectionRule) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Create 创建地址簿规则 // Create create address book rule
// @Tags 我的地址簿规则 // @Tags my address book rules
// @Summary 创建地址簿规则 // @Summary create address book rule
// @Description 创建地址簿规则 // @Description create address book rule
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息" // @Param body body model.AddressBookCollectionRule true "address book rule info"
// @Success 200 {object} response.Response{data=model.AddressBookCollection} // @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection_rule/create [post] // @Router /admin/my/address_book_collection_rule/create [post]
@@ -100,21 +100,21 @@ func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.Address
//check to_id //check to_id
if t.Type == model.ShareAddressBookRuleTypePersonal { if t.Type == model.ShareAddressBookRuleTypePersonal {
if t.ToId == t.UserId { if t.ToId == t.UserId {
return "ParamsError", false return "CannotShareToSelf", false
} }
tou := service.AllService.UserService.InfoById(t.ToId) tou := service.AllService.UserService.InfoById(t.ToId)
if tou.Id == 0 { if tou.Id == 0 {
return "ItemNotFound", false return "ItemNotFound", false
} }
//非管理员不能分享给非本组织用户 //non-admins cannot share with users outside their own organization
if tou.GroupId != u.GroupId { //if tou.GroupId != u.GroupId {
return "NoAccess", false // return "NoAccess", false
} //}
} else if t.Type == model.ShareAddressBookRuleTypeGroup { } else if t.Type == model.ShareAddressBookRuleTypeGroup {
//非管理员不能分享给其他组 //non-admins cannot share with other groups
if t.ToId != u.GroupId { //if t.ToId != u.GroupId {
return "NoAccess", false // return "NoAccess", false
} //}
tog := service.AllService.GroupService.InfoById(t.ToId) tog := service.AllService.GroupService.InfoById(t.ToId)
if tog.Id == 0 { if tog.Id == 0 {
@@ -123,8 +123,8 @@ func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.Address
} else { } else {
return "ParamsError", false return "ParamsError", false
} }
// 重复检查 // duplicate check
ex := service.AllService.AddressBookService.RulePersonalInfoByToIdAndCid(t.ToId, t.CollectionId) ex := service.AllService.AddressBookService.RuleInfoByToIdAndCid(t.Type, t.ToId, t.CollectionId)
if t.Id == 0 && ex.Id > 0 { if t.Id == 0 && ex.Id > 0 {
return "ItemExists", false return "ItemExists", false
} }
@@ -134,13 +134,13 @@ func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.Address
return "", true return "", true
} }
// Update 编辑 // Update edit
// @Tags 我的地址簿规则 // @Tags my address book rules
// @Summary 地址簿规则编辑 // @Summary edit address book rule
// @Description 地址簿规则编辑 // @Description edit address book rule
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息" // @Param body body model.AddressBookCollectionRule true "address book rule info"
// @Success 200 {object} response.Response{data=model.AddressBookCollection} // @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection_rule/update [post] // @Router /admin/my/address_book_collection_rule/update [post]
@@ -185,13 +185,13 @@ func (abcr *AddressBookCollectionRule) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete 删除 // Delete delete
// @Tags 我的地址簿规则 // @Tags my address book rules
// @Summary 地址簿规则删除 // @Summary delete address book rule
// @Description 地址簿规则删除 // @Description delete address book rule
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息" // @Param body body model.AddressBookCollectionRule true "address book rule info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection_rule/delete [post] // @Router /admin/my/address_book_collection_rule/delete [post]
+17 -17
View File
@@ -13,15 +13,15 @@ import (
type LoginLog struct { type LoginLog struct {
} }
// List 列表 // List list
// @Tags 我的登录日志 // @Tags my login logs
// @Summary 登录日志列表 // @Summary login log list
// @Description 登录日志列表 // @Description login log list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Param user_id query int false "用户ID" // @Param user_id query int false "userID"
// @Success 200 {object} response.Response{data=model.LoginLogList} // @Success 200 {object} response.Response{data=model.LoginLogList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/login_log/list [get] // @Router /admin/my/login_log/list [get]
@@ -40,13 +40,13 @@ func (ct *LoginLog) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Delete 删除 // Delete delete
// @Tags 我的登录日志 // @Tags my login logs
// @Summary 登录日志删除 // @Summary delete login log
// @Description 登录日志删除 // @Description delete login log
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.LoginLog true "登录日志信息" // @Param body body model.LoginLog true "login log info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/login_log/delete [post] // @Router /admin/my/login_log/delete [post]
@@ -81,13 +81,13 @@ func (ct *LoginLog) Delete(c *gin.Context) {
response.Fail(c, 101, err.Error()) response.Fail(c, 101, err.Error())
} }
// BatchDelete 删除 // BatchDelete delete
// @Tags 我的登录日志 // @Tags my login logs
// @Summary 登录日志批量删除 // @Summary batch delete login logs
// @Description 登录日志批量删除 // @Description batch delete login logs
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.LoginLogIds true "登录日志" // @Param body body admin.LoginLogIds true "login log"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/login_log/batchDelete [post] // @Router /admin/my/login_log/batchDelete [post]
+9 -9
View File
@@ -12,18 +12,18 @@ import (
type Peer struct { type Peer struct {
} }
// List 列表 // List list
// @Tags 我的设备 // @Tags my devices
// @Summary 设备列表 // @Summary device list
// @Description 设备列表 // @Description device list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Param time_ago query int false "时间" // @Param time_ago query int false "time"
// @Param id query string false "ID" // @Param id query string false "ID"
// @Param hostname query string false "主机名" // @Param hostname query string false "hostname"
// @Param uuids query string false "uuids 用逗号分隔" // @Param uuids query string false "uuids separated by commas"
// @Success 200 {object} response.Response{data=model.PeerList} // @Success 200 {object} response.Response{data=model.PeerList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/peer/list [get] // @Router /admin/my/peer/list [get]
+15 -15
View File
@@ -12,14 +12,14 @@ import (
type ShareRecord struct { type ShareRecord struct {
} }
// List 分享记录列表 // List share record list
// @Tags 我的分享记录 // @Tags my share records
// @Summary 分享记录列表 // @Summary share record list
// @Description 分享记录列表 // @Description share record list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/share_record/list [get] // @Router /admin/my/share_record/list [get]
@@ -37,13 +37,13 @@ func (sr *ShareRecord) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Delete 分享记录删除 // Delete delete share record
// @Tags 我的分享记录 // @Tags my share records
// @Summary 分享记录删除 // @Summary delete share record
// @Description 分享记录删除 // @Description delete share record
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.ShareRecordForm true "分享记录信息" // @Param body body admin.ShareRecordForm true "share record info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/share_record/delete [post] // @Router /admin/my/share_record/delete [post]
@@ -78,10 +78,10 @@ func (sr *ShareRecord) Delete(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
} }
// BatchDelete 批量删除我的分享记录 // BatchDelete batch delete my share records
// @Tags 我的 // @Tags my
// @Summary 批量删除我的分享记录 // @Summary batch delete my share records
// @Description 批量删除我的分享记录 // @Description batch delete my share records
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.PeerShareRecordBatchDeleteForm true "id" // @Param body body admin.PeerShareRecordBatchDeleteForm true "id"
+23 -23
View File
@@ -11,16 +11,16 @@ import (
type Tag struct{} type Tag struct{}
// List 列表 // List list
// @Tags 我的标签 // @Tags my tags
// @Summary 标签列表 // @Summary tag list
// @Description 标签列表 // @Description tag list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Param is_my query int false "是否是我的" // @Param is_my query int false "whether it is mine"
// @Param user_id query int false "用户id" // @Param user_id query int false "userid"
// @Success 200 {object} response.Response{data=model.TagList} // @Success 200 {object} response.Response{data=model.TagList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/tag/list [get] // @Router /admin/my/tag/list [get]
@@ -45,13 +45,13 @@ func (ct *Tag) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Create 创建标签 // Create create tag
// @Tags 我的标签 // @Tags my tags
// @Summary 创建标签 // @Summary create tag
// @Description 创建标签 // @Description create tag
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.TagForm true "标签信息" // @Param body body admin.TagForm true "tag info"
// @Success 200 {object} response.Response{data=model.Tag} // @Success 200 {object} response.Response{data=model.Tag}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/tag/create [post] // @Router /admin/my/tag/create [post]
@@ -78,13 +78,13 @@ func (ct *Tag) Create(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Update 编辑 // Update edit
// @Tags 我的标签 // @Tags my tags
// @Summary 标签编辑 // @Summary edit tag
// @Description 标签编辑 // @Description edit tag
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.TagForm true "标签信息" // @Param body body admin.TagForm true "tag info"
// @Success 200 {object} response.Response{data=model.Tag} // @Success 200 {object} response.Response{data=model.Tag}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/tag/update [post] // @Router /admin/my/tag/update [post]
@@ -133,13 +133,13 @@ func (ct *Tag) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete 删除 // Delete delete
// @Tags 标签 // @Tags tag
// @Summary 标签删除 // @Summary delete tag
// @Description 标签删除 // @Description delete tag
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.TagForm true "标签信息" // @Param body body admin.TagForm true "tag info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/my/tag/delete [post] // @Router /admin/my/tag/delete [post]
+25 -24
View File
@@ -1,13 +1,14 @@
package admin package admin
import ( import (
"strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global" "github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin" "github.com/lejianwen/rustdesk-api/v2/http/request/admin"
adminReq "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/http/response"
"github.com/lejianwen/rustdesk-api/v2/service" "github.com/lejianwen/rustdesk-api/v2/service"
"strconv"
) )
type Oauth struct { type Oauth struct {
@@ -63,21 +64,21 @@ func (o *Oauth) ToBind(c *gin.Context) {
}) })
} }
// Confirm 确认授权登录 // Confirm confirm authorized login
func (o *Oauth) Confirm(c *gin.Context) { func (o *Oauth) Confirm(c *gin.Context) {
j := &adminReq.OauthConfirmForm{} j := &adminReq.OauthConfirmForm{}
err := c.ShouldBindJSON(j) err := c.ShouldBindJSON(j)
if err != nil { if err != nil {
response.Fail(c, 101, "参数错误"+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return return
} }
if j.Code == "" { if j.Code == "" {
response.Fail(c, 101, "参数错误: code 不存在") response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return return
} }
v := service.AllService.OauthService.GetOauthCache(j.Code) v := service.AllService.OauthService.GetOauthCache(j.Code)
if v == nil { if v == nil {
response.Fail(c, 101, "授权已过期") response.Fail(c, 101, response.TranslateMsg(c, "OauthExpired"))
return return
} }
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
@@ -139,8 +140,8 @@ func (o *Oauth) Unbind(c *gin.Context) {
// Detail Oauth // Detail Oauth
// @Tags Oauth // @Tags Oauth
// @Summary Oauth详情 // @Summary Oauthdetails
// @Description Oauth详情 // @Description Oauthdetails
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -160,13 +161,13 @@ func (o *Oauth) Detail(c *gin.Context) {
return return
} }
// Create 创建Oauth // Create createOauth
// @Tags Oauth // @Tags Oauth
// @Summary 创建Oauth // @Summary createOauth
// @Description 创建Oauth // @Description createOauth
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.OauthForm true "Oauth信息" // @Param body body admin.OauthForm true "Oauthinfo"
// @Success 200 {object} response.Response{data=model.Oauth} // @Success 200 {object} response.Response{data=model.Oauth}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/oauth/create [post] // @Router /admin/oauth/create [post]
@@ -201,14 +202,14 @@ func (o *Oauth) Create(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// List 列表 // List list
// @Tags Oauth // @Tags Oauth
// @Summary Oauth列表 // @Summary Oauthlist
// @Description Oauth列表 // @Description Oauthlist
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Success 200 {object} response.Response{data=model.OauthList} // @Success 200 {object} response.Response{data=model.OauthList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/oauth/list [get] // @Router /admin/oauth/list [get]
@@ -223,13 +224,13 @@ func (o *Oauth) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Update 编辑 // Update edit
// @Tags Oauth // @Tags Oauth
// @Summary Oauth编辑 // @Summary Oauthedit
// @Description Oauth编辑 // @Description Oauthedit
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.OauthForm true "Oauth信息" // @Param body body admin.OauthForm true "Oauthinfo"
// @Success 200 {object} response.Response{data=model.OauthList} // @Success 200 {object} response.Response{data=model.OauthList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/oauth/update [post] // @Router /admin/oauth/update [post]
@@ -258,13 +259,13 @@ func (o *Oauth) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete 删除 // Delete delete
// @Tags Oauth // @Tags Oauth
// @Summary Oauth删除 // @Summary Oauthdelete
// @Description Oauth删除 // @Description Oauthdelete
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.OauthForm true "Oauth信息" // @Param body body admin.OauthForm true "Oauthinfo"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/oauth/delete [post] // @Router /admin/oauth/delete [post]
+43 -34
View File
@@ -14,10 +14,10 @@ import (
type Peer struct { type Peer struct {
} }
// Detail 设备 // Detail device
// @Tags 设备 // @Tags device
// @Summary 设备详情 // @Summary device details
// @Description 设备详情 // @Description device details
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -37,13 +37,13 @@ func (ct *Peer) Detail(c *gin.Context) {
return return
} }
// Create 创建设备 // Create create device
// @Tags 设备 // @Tags device
// @Summary 创建设备 // @Summary create device
// @Description 创建设备 // @Description create device
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.PeerForm true "设备信息" // @Param body body admin.PeerForm true "device info"
// @Success 200 {object} response.Response{data=model.Peer} // @Success 200 {object} response.Response{data=model.Peer}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/peer/create [post] // @Router /admin/peer/create [post]
@@ -68,18 +68,18 @@ func (ct *Peer) Create(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// List 列表 // List list
// @Tags 设备 // @Tags device
// @Summary 设备列表 // @Summary device list
// @Description 设备列表 // @Description device list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Param time_ago query int false "时间" // @Param time_ago query int false "time"
// @Param id query string false "ID" // @Param id query string false "ID"
// @Param hostname query string false "主机名" // @Param hostname query string false "hostname"
// @Param uuids query string false "uuids 用逗号分隔" // @Param uuids query string false "uuids separated by commas"
// @Success 200 {object} response.Response{data=model.PeerList} // @Success 200 {object} response.Response{data=model.PeerList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/peer/list [get] // @Router /admin/peer/list [get]
@@ -108,17 +108,26 @@ func (ct *Peer) List(c *gin.Context) {
if query.Uuids != "" { if query.Uuids != "" {
tx.Where("uuid in (?)", 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) response.Success(c, res)
} }
// Update 编辑 // Update edit
// @Tags 设备 // @Tags device
// @Summary 设备编辑 // @Summary edit device
// @Description 设备编辑 // @Description edit device
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.PeerForm true "设备信息" // @Param body body admin.PeerForm true "device info"
// @Success 200 {object} response.Response{data=model.Peer} // @Success 200 {object} response.Response{data=model.Peer}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/peer/update [post] // @Router /admin/peer/update [post]
@@ -147,13 +156,13 @@ func (ct *Peer) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete 删除 // Delete delete
// @Tags 设备 // @Tags device
// @Summary 设备删除 // @Summary delete device
// @Description 设备删除 // @Description delete device
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.PeerForm true "设备信息" // @Param body body admin.PeerForm true "device info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/peer/delete [post] // @Router /admin/peer/delete [post]
@@ -183,13 +192,13 @@ func (ct *Peer) Delete(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
} }
// BatchDelete 批量删除 // BatchDelete batch delete
// @Tags 设备 // @Tags device
// @Summary 批量设备删除 // @Summary batch delete devices
// @Description 批量设备删除 // @Description batch delete devices
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.PeerBatchDeleteForm true "设备id" // @Param body body admin.PeerBatchDeleteForm true "deviceid"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/peer/batchDelete [post] // @Router /admin/peer/batchDelete [post]
@@ -223,7 +232,7 @@ func (ct *Peer) SimpleData(c *gin.Context) {
return return
} }
res := service.AllService.PeerService.List(1, 99999, func(tx *gorm.DB) { res := service.AllService.PeerService.List(1, 99999, func(tx *gorm.DB) {
//可以公开的情报 //information that can be made public
tx.Select("id,version") tx.Select("id,version")
tx.Where("id in (?)", f.Ids) tx.Where("id in (?)", f.Ids)
}) })
+11 -2
View File
@@ -25,7 +25,7 @@ func (r *Rustdesk) CmdList(c *gin.Context) {
return return
} }
res := service.AllService.ServerCmdService.List(q.Page, 9999) res := service.AllService.ServerCmdService.List(q.Page, 9999)
//在列表前添加系统命令 //add system commands before the list
list := make([]*model.ServerCmd, 0) list := make([]*model.ServerCmd, 0)
list = append(list, model.SysIdServerCmds...) list = append(list, model.SysIdServerCmds...)
list = append(list, model.SysRelayServerCmds...) list = append(list, model.SysRelayServerCmds...)
@@ -119,7 +119,16 @@ func (r *Rustdesk) SendCmd(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")) response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return return
} }
res, err := service.AllService.ServerCmdService.SendCmd(rc.Target, rc.Cmd, rc.Option)
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 { if err != nil {
response.Fail(c, 101, err.Error()) response.Fail(c, 101, err.Error())
return return
+16 -16
View File
@@ -12,15 +12,15 @@ import (
type ShareRecord struct { type ShareRecord struct {
} }
// List 列表 // List list
// @Tags 分享记录 // @Tags share record
// @Summary 分享记录列表 // @Summary share record list
// @Description 分享记录列表 // @Description share record list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param user_id query int false "用户ID" // @Param user_id query int false "userID"
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/share_record/list [get] // @Router /admin/share_record/list [get]
@@ -39,13 +39,13 @@ func (sr *ShareRecord) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Delete 删除 // Delete delete
// @Tags 分享记录 // @Tags share record
// @Summary 分享记录删除 // @Summary delete share record
// @Description 分享记录删除 // @Description delete share record
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.ShareRecordForm true "分享记录信息" // @Param body body admin.ShareRecordForm true "share record info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/share_record/delete [post] // @Router /admin/share_record/delete [post]
@@ -75,10 +75,10 @@ func (sr *ShareRecord) Delete(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
} }
// BatchDelete 批量删除 // BatchDelete batch delete
// @Tags 分享记录 // @Tags share record
// @Summary 批量分享记录 // @Summary batch share records
// @Description 批量分享记录 // @Description batch share records
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.PeerShareRecordBatchDeleteForm true "id" // @Param body body admin.PeerShareRecordBatchDeleteForm true "id"
+27 -27
View File
@@ -13,10 +13,10 @@ import (
type Tag struct { type Tag struct {
} }
// Detail 标签 // Detail tag
// @Tags 标签 // @Tags tag
// @Summary 标签详情 // @Summary tag details
// @Description 标签详情 // @Description tag details
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -41,13 +41,13 @@ func (ct *Tag) Detail(c *gin.Context) {
return return
} }
// Create 创建标签 // Create create tag
// @Tags 标签 // @Tags tag
// @Summary 创建标签 // @Summary create tag
// @Description 创建标签 // @Description create tag
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.TagForm true "标签信息" // @Param body body admin.TagForm true "tag info"
// @Success 200 {object} response.Response{data=model.Tag} // @Success 200 {object} response.Response{data=model.Tag}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/tag/create [post] // @Router /admin/tag/create [post]
@@ -76,16 +76,16 @@ func (ct *Tag) Create(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// List 列表 // List list
// @Tags 标签 // @Tags tag
// @Summary 标签列表 // @Summary tag list
// @Description 标签列表 // @Description tag list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Param is_my query int false "是否是我的" // @Param is_my query int false "whether it is mine"
// @Param user_id query int false "用户id" // @Param user_id query int false "userid"
// @Success 200 {object} response.Response{data=model.TagList} // @Success 200 {object} response.Response{data=model.TagList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/tag/list [get] // @Router /admin/tag/list [get]
@@ -110,13 +110,13 @@ func (ct *Tag) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Update 编辑 // Update edit
// @Tags 标签 // @Tags tag
// @Summary 标签编辑 // @Summary edit tag
// @Description 标签编辑 // @Description edit tag
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.TagForm true "标签信息" // @Param body body admin.TagForm true "tag info"
// @Success 200 {object} response.Response{data=model.Tag} // @Success 200 {object} response.Response{data=model.Tag}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/tag/update [post] // @Router /admin/tag/update [post]
@@ -150,13 +150,13 @@ func (ct *Tag) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete 删除 // Delete delete
// @Tags 标签 // @Tags tag
// @Summary 标签删除 // @Summary delete tag
// @Description 标签删除 // @Description delete tag
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.TagForm true "标签信息" // @Param body body admin.TagForm true "tag info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/tag/delete [post] // @Router /admin/tag/delete [post]
+65 -74
View File
@@ -8,6 +8,7 @@ import (
adResp "github.com/lejianwen/rustdesk-api/v2/http/response/admin" adResp "github.com/lejianwen/rustdesk-api/v2/http/response/admin"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service" "github.com/lejianwen/rustdesk-api/v2/service"
"github.com/lejianwen/rustdesk-api/v2/utils"
"gorm.io/gorm" "gorm.io/gorm"
"strconv" "strconv"
) )
@@ -15,10 +16,10 @@ import (
type User struct { type User struct {
} }
// Detail 管理员 // Detail administrator
// @Tags 用户 // @Tags user
// @Summary 管理员详情 // @Summary administrator details
// @Description 管理员详情 // @Description administrator details
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -38,13 +39,13 @@ func (ct *User) Detail(c *gin.Context) {
return return
} }
// Create 管理员 // Create administrator
// @Tags 用户 // @Tags user
// @Summary 创建管理员 // @Summary create administrator
// @Description 创建管理员 // @Description create administrator
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.UserForm true "管理员信息" // @Param body body admin.UserForm true "administrator info"
// @Success 200 {object} response.Response{data=model.User} // @Success 200 {object} response.Response{data=model.User}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/user/create [post] // @Router /admin/user/create [post]
@@ -69,15 +70,15 @@ func (ct *User) Create(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// List 列表 // List list
// @Tags 用户 // @Tags user
// @Summary 管理员列表 // @Summary administrator list
// @Description 管理员列表 // @Description administrator list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Param username query int false "账户" // @Param username query int false "account"
// @Success 200 {object} response.Response{data=model.UserList} // @Success 200 {object} response.Response{data=model.UserList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/user/list [get] // @Router /admin/user/list [get]
@@ -96,13 +97,13 @@ func (ct *User) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Update 编辑 // Update edit
// @Tags 用户 // @Tags user
// @Summary 管理员编辑 // @Summary edit administrator
// @Description 管理员编辑 // @Description edit administrator
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.UserForm true "用户信息" // @Param body body admin.UserForm true "user info"
// @Success 200 {object} response.Response{data=model.User} // @Success 200 {object} response.Response{data=model.User}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/user/update [post] // @Router /admin/user/update [post]
@@ -131,13 +132,13 @@ func (ct *User) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete 删除 // Delete delete
// @Tags 用户 // @Tags user
// @Summary 管理员删除 // @Summary delete administrator
// @Description 管理员编删除 // @Description delete administrator
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.UserForm true "用户信息" // @Param body body admin.UserForm true "user info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/user/delete [post] // @Router /admin/user/delete [post]
@@ -167,13 +168,13 @@ func (ct *User) Delete(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
} }
// UpdatePassword 修改密码 // UpdatePassword change password
// @Tags 用户 // @Tags user
// @Summary 修改密码 // @Summary change password
// @Description 修改密码 // @Description change password
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.UserPasswordForm true "用户信息" // @Param body body admin.UserPasswordForm true "user info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/user/updatePassword [post] // @Router /admin/user/updatePassword [post]
@@ -202,10 +203,10 @@ func (ct *User) UpdatePassword(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Current 当前用户 // Current current user
// @Tags 用户 // @Tags user
// @Summary 当前用户 // @Summary current user
// @Description 当前用户 // @Description current user
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response{data=adResp.LoginPayload} // @Success 200 {object} response.Response{data=adResp.LoginPayload}
@@ -219,13 +220,13 @@ func (ct *User) Current(c *gin.Context) {
responseLoginSuccess(c, u, t) responseLoginSuccess(c, u, t)
} }
// ChangeCurPwd 修改当前用户密码 // ChangeCurPwd change the current user's password
// @Tags 用户 // @Tags user
// @Summary 修改当前用户密码 // @Summary change the current user's password
// @Description 修改当前用户密码 // @Description change the current user's password
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.ChangeCurPasswordForm true "用户信息" // @Param body body admin.ChangeCurPasswordForm true "user info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/user/changeCurPwd [post] // @Router /admin/user/changeCurPwd [post]
@@ -243,11 +244,10 @@ func (ct *User) ChangeCurPwd(c *gin.Context) {
return return
} }
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
// If the password is not empty, the old password is verified // Verify the old password only when the account already has one set
// otherwise, the old password is not verified
if !service.AllService.UserService.IsPasswordEmptyByUser(u) { if !service.AllService.UserService.IsPasswordEmptyByUser(u) {
oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword) ok, _, err := utils.VerifyPassword(u.Password, f.OldPassword)
if u.Password != oldPwd { if err != nil || !ok {
response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError")) response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError"))
return return
} }
@@ -261,9 +261,9 @@ func (ct *User) ChangeCurPwd(c *gin.Context) {
} }
// MyOauth // MyOauth
// @Tags 用户 // @Tags user
// @Summary 我的授权 // @Summary my authorizations
// @Description 我的授权 // @Description my authorizations
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response{data=[]adResp.UserOauthItem} // @Success 200 {object} response.Response{data=[]adResp.UserOauthItem}
@@ -296,32 +296,12 @@ func (ct *User) MyOauth(c *gin.Context) {
// groupUsers // groupUsers
func (ct *User) GroupUsers(c *gin.Context) { func (ct *User) GroupUsers(c *gin.Context) {
q := &admin.GroupUsersQuery{} aG := service.AllService.GroupService.List(1, 999, nil)
if err := c.ShouldBindJSON(q); err != nil { aU := service.AllService.UserService.List(1, 9999, nil)
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Success(c, gin.H{
return "groups": aG.Groups,
} "users": aU.Users,
u := service.AllService.UserService.CurUser(c)
gid := u.GroupId
uid := u.Id
if service.AllService.UserService.IsAdmin(u) && q.UserId > 0 {
nu := service.AllService.UserService.InfoById(q.UserId)
gid = nu.GroupId
uid = q.UserId
}
res := service.AllService.UserService.List(1, 999, func(tx *gorm.DB) {
tx.Where("group_id = ?", gid)
}) })
var data []*adResp.GroupUsersPayload
for _, _u := range res.Users {
gup := &adResp.GroupUsersPayload{}
gup.FromUser(_u)
if _u.Id == uid {
gup.Status = 0
}
data = append(data, gup)
}
response.Success(c, data)
} }
// Register // Register
@@ -340,12 +320,23 @@ func (ct *User) Register(c *gin.Context) {
response.Fail(c, 101, errList[0]) response.Fail(c, 101, errList[0])
return return
} }
u := service.AllService.UserService.Register(f.Username, f.Email, f.Password) regStatus := model.StatusCode(global.Config.App.RegisterStatus)
// the registration status may not be configured; enabled by default
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 { if u == nil || u.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")) response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed"))
return return
} }
// 注册成功后自动登录 if regStatus == model.COMMON_STATUS_DISABLED {
// requires administrator approval
response.Fail(c, 101, response.TranslateMsg(c, "RegisterSuccessWaitAdminConfirm"))
return
}
// automatically log in after successful registration
ut := service.AllService.UserService.Login(u, &model.LoginLog{ ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id, UserId: u.Id,
Client: model.LoginLogClientWebAdmin, Client: model.LoginLogClientWebAdmin,
+17 -17
View File
@@ -13,15 +13,15 @@ import (
type UserToken struct { type UserToken struct {
} }
// List 列表 // List list
// @Tags 登录凭证 // @Tags login credential
// @Summary 登录凭证列表 // @Summary login credential list
// @Description 登录凭证列表 // @Description login credential list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param page_size query int false "页大小" // @Param page_size query int false "page size"
// @Param user_id query int false "用户ID" // @Param user_id query int false "userID"
// @Success 200 {object} response.Response{data=model.UserTokenList} // @Success 200 {object} response.Response{data=model.UserTokenList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/user_token/list [get] // @Router /admin/user_token/list [get]
@@ -41,13 +41,13 @@ func (ct *UserToken) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Delete 删除 // Delete delete
// @Tags 登录凭证 // @Tags login credential
// @Summary 登录凭证删除 // @Summary delete login credential
// @Description 登录凭证删除 // @Description delete login credential
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.UserToken true "登录凭证信息" // @Param body body model.UserToken true "login credential info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/user_token/delete [post] // @Router /admin/user_token/delete [post]
@@ -82,13 +82,13 @@ func (ct *UserToken) Delete(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
} }
// BatchDelete 批量删除 // BatchDelete batch delete
// @Tags 登录凭证 // @Tags login credential
// @Summary 登录凭证批量删除 // @Summary batch delete login credentials
// @Description 登录凭证批量删除 // @Description batch delete login credentials
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.UserTokenBatchDeleteForm true "登录凭证信息" // @Param body body admin.UserTokenBatchDeleteForm true "login credential info"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/user_token/batchDelete [post] // @Router /admin/user_token/batchDelete [post]
+58 -58
View File
@@ -20,9 +20,9 @@ type Ab struct {
} }
// Ab // Ab
// @Tags 地址 // @Tags address
// @Summary 地址列表 // @Summary address list
// @Description 地址列表 // @Description address list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
@@ -36,7 +36,7 @@ func (a *Ab) Ab(c *gin.Context) {
tags := service.AllService.TagService.ListByUserIdAndCollectionId(user.Id, 0) tags := service.AllService.TagService.ListByUserIdAndCollectionId(user.Id, 0)
tagColors := map[string]uint{} tagColors := map[string]uint{}
//将tags中的name转成一个以逗号分割的字符串 // convert the name in tags into a comma-separated string
var tagNames []string var tagNames []string
for _, tag := range tags.Tags { for _, tag := range tags.Tags {
tagNames = append(tagNames, tag.Name) tagNames = append(tagNames, tag.Name)
@@ -56,12 +56,12 @@ func (a *Ab) Ab(c *gin.Context) {
} }
// UpAb // UpAb
// @Tags 地址 // @Tags address
// @Summary 地址更新 // @Summary address update
// @Description 地址更新 // @Description address update
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body requstform.AddressBookForm true "地址表单" // @Param body body requstform.AddressBookForm true "address form"
// @Success 200 {string} string "null" // @Success 200 {string} string "null"
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /ab [post] // @Router /ab [post]
@@ -99,9 +99,9 @@ func (a *Ab) UpAb(c *gin.Context) {
} }
// PTags // PTags
// @Tags 地址[Personal] // @Tags address[Personal]
// @Summary 标签 // @Summary tag
// @Description 标签 // @Description tag
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid" // @Param guid path string true "guid"
@@ -128,9 +128,9 @@ func (a *Ab) PTags(c *gin.Context) {
} }
// TagAdd // TagAdd
// @Tags 地址[Personal] // @Tags address[Personal]
// @Summary 标签添加 // @Summary add tag
// @Description 标签 // @Description tag
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid" // @Param guid path string true "guid"
@@ -177,9 +177,9 @@ func (a *Ab) TagAdd(c *gin.Context) {
} }
// TagRename // TagRename
// @Tags 地址[Personal] // @Tags address[Personal]
// @Summary 标签重命名 // @Summary rename tag
// @Description 标签 // @Description tag
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid" // @Param guid path string true "guid"
@@ -229,9 +229,9 @@ func (a *Ab) TagRename(c *gin.Context) {
} }
// TagUpdate // TagUpdate
// @Tags 地址[Personal] // @Tags address[Personal]
// @Summary 标签修改颜色 // @Summary change tag color
// @Description 标签 // @Description tag
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid" // @Param guid path string true "guid"
@@ -275,9 +275,9 @@ func (a *Ab) TagUpdate(c *gin.Context) {
} }
// TagDel // TagDel
// @Tags 地址[Personal] // @Tags address[Personal]
// @Summary 标签删除 // @Summary delete tag
// @Description 标签 // @Description tag
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid" // @Param guid path string true "guid"
@@ -324,9 +324,9 @@ func (a *Ab) TagDel(c *gin.Context) {
} }
// Personal // Personal
// @Tags 地址[Personal] // @Tags address[Personal]
// @Summary 个人地址 // @Summary personal address
// @Description 个人地址 // @Description personal address
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param string body string false "string valid" // @Param string body string false "string valid"
@@ -345,7 +345,7 @@ func (a *Ab) Personal(c *gin.Context) {
*/ */
if global.Config.Rustdesk.Personal == 1 { if global.Config.Rustdesk.Personal == 1 {
guid := a.ComposeGuid(user.GroupId, user.Id, 0) guid := a.ComposeGuid(user.GroupId, user.Id, 0)
//如果返回了guid,后面的请求会有变化 //if it returns guid, subsequent requests will change
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"guid": guid, "guid": guid,
"name": user.Username, "name": user.Username,
@@ -358,9 +358,9 @@ func (a *Ab) Personal(c *gin.Context) {
} }
// Settings // Settings
// @Tags 地址[Personal] // @Tags address[Personal]
// @Summary 设置 // @Summary settings
// @Description 设置 // @Description settings
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param string body string false "string valid" // @Param string body string false "string valid"
@@ -370,18 +370,18 @@ func (a *Ab) Personal(c *gin.Context) {
// @Security BearerAuth // @Security BearerAuth
func (a *Ab) Settings(c *gin.Context) { func (a *Ab) Settings(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"max_peer_one_ab": 0, //最大peer数,0表示不限制 "max_peer_one_ab": 0, //max peer count, 0 means no limit
}) })
} }
// SharedProfiles // SharedProfiles
// @Tags 地址[Personal] // @Tags address[Personal]
// @Summary 共享地址簿 // @Summary shared address book
// @Description 共享 // @Description shared
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param current query int false "页码" // @Param current query int false "page number"
// @Param pageSize query int false "每页数量" // @Param pageSize query int false "items per page"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /ab/shared/profiles [post] // @Router /ab/shared/profiles [post]
@@ -401,14 +401,14 @@ func (a *Ab) SharedProfiles(c *gin.Context) {
}) })
} }
allAbIds := make(map[uint]int) //用map去重,并保留最大Rule allAbIds := make(map[uint]int) //use map to deduplicate and keep the maximum Rule
allUserIds := make(map[uint]*model.User) allUserIds := make(map[uint]*model.User)
rules := service.AllService.AddressBookService.CollectionReadRules(user) rules := service.AllService.AddressBookService.CollectionReadRules(user)
for _, rule := range rules { for _, rule := range rules {
//先判断是否存在 //first check whether it exists
r, ok := allAbIds[rule.CollectionId] r, ok := allAbIds[rule.CollectionId]
if ok { if ok {
//再判断权限大小 //then check the permission level
if r < rule.Rule { if r < rule.Rule {
allAbIds[rule.CollectionId] = rule.Rule allAbIds[rule.CollectionId] = rule.Rule
} }
@@ -448,7 +448,7 @@ func (a *Ab) SharedProfiles(c *gin.Context) {
// ParseGuid // ParseGuid
func (a *Ab) ParseGuid(guid string) (gid, uid, cid uint) { func (a *Ab) ParseGuid(guid string) (gid, uid, cid uint) {
//用-切割 guid // split guid by -
guids := strings.Split(guid, "-") guids := strings.Split(guid, "-")
if len(guids) < 2 { if len(guids) < 2 {
return 0, 0, 0 return 0, 0, 0
@@ -521,13 +521,13 @@ func (a *Ab) CheckGuid(cu *model.User, guid string) (gid, uid, cid uint, err err
} }
// Peers // Peers
// @Tags 地址[Personal] // @Tags address[Personal]
// @Summary 地址列表 // @Summary address list
// @Description 地址 // @Description address
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param current query int false "页码" // @Param current query int false "page number"
// @Param pageSize query int false "每页数量" // @Param pageSize query int false "items per page"
// @Param ab query string false "guid" // @Param ab query string false "guid"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
@@ -557,9 +557,9 @@ func (a *Ab) Peers(c *gin.Context) {
} }
// PeerAdd // PeerAdd
// @Tags 地址[Personal] // @Tags address[Personal]
// @Summary 添加地址 // @Summary add address
// @Description 添加地址 // @Description add address
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid" // @Param guid path string true "guid"
@@ -568,7 +568,7 @@ func (a *Ab) Peers(c *gin.Context) {
// @Router /ab/peer/add/{guid} [post] // @Router /ab/peer/add/{guid} [post]
// @Security BearerAuth // @Security BearerAuth
func (a *Ab) PeerAdd(c *gin.Context) { func (a *Ab) PeerAdd(c *gin.Context) {
// forceAlwaysRelay永远是字符串"false" // forceAlwaysRelay is always the string "false"
//f := &gin.H{} //f := &gin.H{}
f := &requstform.PersonalAddressBookForm{} f := &requstform.PersonalAddressBookForm{}
err := c.ShouldBindJSON(f) err := c.ShouldBindJSON(f)
@@ -613,9 +613,9 @@ func (a *Ab) PeerAdd(c *gin.Context) {
} }
// PeerDel // PeerDel
// @Tags 地址[Personal] // @Tags address[Personal]
// @Summary 删除地址 // @Summary delete address
// @Description 删除地址 // @Description delete address
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid" // @Param guid path string true "guid"
@@ -661,9 +661,9 @@ func (a *Ab) PeerDel(c *gin.Context) {
} }
// PeerUpdate // PeerUpdate
// @Tags 地址[Personal] // @Tags address[Personal]
// @Summary 更新地址 // @Summary update address
// @Description 更新地址 // @Description update address
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid" // @Param guid path string true "guid"
@@ -693,7 +693,7 @@ func (a *Ab) PeerUpdate(c *gin.Context) {
return return
} }
//fmt.Println(f) //fmt.Println(f)
//判断f["Id"]是否存在 // determine whether f["Id"] exists
fid, ok := f["id"] fid, ok := f["id"]
if !ok { if !ok {
response.Error(c, response.TranslateMsg(c, "ParamsError")) response.Error(c, response.TranslateMsg(c, "ParamsError"))
@@ -706,9 +706,9 @@ func (a *Ab) PeerUpdate(c *gin.Context) {
response.Error(c, response.TranslateMsg(c, "ItemNotFound")) response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return return
} }
//允许的字段 //allowed fields
allowUp := []string{"password", "hash", "tags", "alias"} allowUp := []string{"password", "hash", "tags", "alias"}
//f中的字段如果不在allowUp中,就删除 // if a field in f is not in allowUp, delete it
for k := range f { for k := range f {
if !utils.InArray(k, allowUp) { if !utils.InArray(k, allowUp) {
delete(f, k) delete(f, k)
+8 -8
View File
@@ -14,12 +14,12 @@ type Audit struct {
} }
// AuditConn // AuditConn
// @Tags 审计 // @Tags audit
// @Summary 审计连接 // @Summary audit connection
// @Description 审计连接 // @Description audit connection
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body request.AuditConnForm true "审计连接" // @Param body body request.AuditConnForm true "audit connection"
// @Success 200 {string} string "" // @Success 200 {string} string ""
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /audit/conn [post] // @Router /audit/conn [post]
@@ -59,12 +59,12 @@ func (a *Audit) AuditConn(c *gin.Context) {
} }
// AuditFile // AuditFile
// @Tags 审计 // @Tags audit
// @Summary 审计文件 // @Summary audit file
// @Description 审计文件 // @Description audit file
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body request.AuditFileForm true "审计文件" // @Param body body request.AuditFileForm true "audit file"
// @Success 200 {string} string "" // @Success 200 {string} string ""
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /audit/file [post] // @Router /audit/file [post]
+21 -21
View File
@@ -13,15 +13,15 @@ import (
type Group struct { type Group struct {
} }
// Users 用户列表 // Users user list
// @Tags 群组 // @Tags group
// @Summary 用户列表 // @Summary user list
// @Description 用户列表 // @Description user list
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param pageSize query int false "每页数量" // @Param pageSize query int false "items per page"
// @Param status query int false "状态" // @Param status query int false "status"
// @Param accessible query string false "accessible" // @Param accessible query string false "accessible"
// @Success 200 {object} response.DataResponse{data=[]apiResp.UserPayload} // @Success 200 {object} response.DataResponse{data=[]apiResp.UserPayload}
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
@@ -38,7 +38,7 @@ func (g *Group) Users(c *gin.Context) {
gr := service.AllService.GroupService.InfoById(u.GroupId) gr := service.AllService.GroupService.InfoById(u.GroupId)
userList := &model.UserList{} userList := &model.UserList{}
if !*u.IsAdmin && gr.Type != model.GroupTypeShare { if !*u.IsAdmin && gr.Type != model.GroupTypeShare {
//仅能获取到自己 //can only retrieve one's own
userList.Users = append(userList.Users, u) userList.Users = append(userList.Users, u)
userList.Total = 1 userList.Total = 1
} else { } else {
@@ -58,14 +58,14 @@ func (g *Group) Users(c *gin.Context) {
} }
// Peers // Peers
// @Tags 群组 // @Tags group
// @Summary 机器 // @Summary machine
// @Description 机器 // @Description machine
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param pageSize query int false "每页数量" // @Param pageSize query int false "items per page"
// @Param status query int false "状态" // @Param status query int false "status"
// @Param accessible query string false "accessible" // @Param accessible query string false "accessible"
// @Success 200 {object} response.DataResponse // @Success 200 {object} response.DataResponse
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
@@ -82,7 +82,7 @@ func (g *Group) Peers(c *gin.Context) {
gr := service.AllService.GroupService.InfoById(u.GroupId) gr := service.AllService.GroupService.InfoById(u.GroupId)
users := make([]*model.User, 0, 1) users := make([]*model.User, 0, 1)
if !*u.IsAdmin && gr.Type != model.GroupTypeShare { if !*u.IsAdmin && gr.Type != model.GroupTypeShare {
//仅能获取到自己 //can only retrieve one's own
users = append(users, u) users = append(users, u)
} else { } else {
users = service.AllService.UserService.ListIdAndNameByGroupId(u.GroupId) users = service.AllService.UserService.ListIdAndNameByGroupId(u.GroupId)
@@ -122,14 +122,14 @@ func (g *Group) Peers(c *gin.Context) {
} }
// Device // Device
// @Tags 群组 // @Tags group
// @Summary 设备 // @Summary device
// @Description 机器 // @Description machine
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "页码" // @Param page query int false "page number"
// @Param pageSize query int false "每页数量" // @Param pageSize query int false "items per page"
// @Param status query int false "状态" // @Param status query int false "status"
// @Param accessible query string false "accessible" // @Param accessible query string false "accessible"
// @Success 200 {object} response.DataResponse // @Success 200 {object} response.DataResponse
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
+18 -23
View File
@@ -7,17 +7,16 @@ import (
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service" "github.com/lejianwen/rustdesk-api/v2/service"
"net/http" "net/http"
"os"
"time" "time"
) )
type Index struct { type Index struct {
} }
// Index 首页 // Index home
// @Tags 首页 // @Tags home
// @Summary 首页 // @Summary home
// @Description 首页 // @Description home
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
@@ -30,10 +29,10 @@ func (i *Index) Index(c *gin.Context) {
) )
} }
// Heartbeat 心跳 // Heartbeat heartbeat
// @Tags 首页 // @Tags home
// @Summary 心跳 // @Summary heartbeat
// @Description 心跳 // @Description heartbeat
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} nil // @Success 200 {object} nil
@@ -50,37 +49,33 @@ func (i *Index) Heartbeat(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{}) c.JSON(http.StatusOK, gin.H{})
return return
} }
peer := service.AllService.PeerService.FindByUuid(info.Uuid) peer := service.AllService.PeerService.FindById(info.Id)
if peer == nil || peer.RowId == 0 { if peer == nil || peer.RowId == 0 {
c.JSON(http.StatusOK, gin.H{}) c.JSON(http.StatusOK, gin.H{})
return return
} }
//如果在40s以内则不更新 // do not update if within 40s
if time.Now().Unix()-peer.LastOnlineTime > 40 { if time.Now().Unix()-peer.LastOnlineTime >= 30 {
upp := &model.Peer{RowId: peer.RowId, LastOnlineTime: time.Now().Unix(), LastOnlineIp: c.ClientIP()} upp := &model.Peer{RowId: peer.RowId, LastOnlineTime: time.Now().Unix(), LastOnlineIp: c.ClientIP()}
service.AllService.PeerService.Update(upp) service.AllService.PeerService.Update(upp)
} }
c.JSON(http.StatusOK, gin.H{}) c.JSON(http.StatusOK, gin.H{})
} }
// Version 版本 // Version version
// @Tags 首页 // @Tags home
// @Summary 版本 // @Summary version
// @Description 版本 // @Description version
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /version [get] // @Router /version [get]
func (i *Index) Version(c *gin.Context) { func (i *Index) Version(c *gin.Context) {
//读取resources/version文件 // read the resources/version file
v, err := os.ReadFile("resources/version") v := service.AllService.AppService.GetAppVersion()
if err != nil {
response.Fail(c, 101, err.Error())
return
}
response.Success( response.Success(
c, c,
string(v), v,
) )
} }
+20 -12
View File
@@ -16,13 +16,13 @@ import (
type Login struct { type Login struct {
} }
// Login 登录 // Login login
// @Tags 登录 // @Tags login
// @Summary 登录 // @Summary login
// @Description 登录 // @Description login
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body api.LoginForm true "登录表单" // @Param body body api.LoginForm true "login form"
// @Success 200 {object} apiResp.LoginRes // @Success 200 {object} apiResp.LoginRes
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /login [post] // @Router /login [post]
@@ -31,10 +31,16 @@ func (l *Login) Login(c *gin.Context) {
response.Error(c, response.TranslateMsg(c, "PwdLoginDisabled")) response.Error(c, response.TranslateMsg(c, "PwdLoginDisabled"))
return return
} }
// check login limit
loginLimiter := global.LoginLimiter
clientIp := c.ClientIP()
f := &api.LoginForm{} f := &api.LoginForm{}
err := c.ShouldBindJSON(f) err := c.ShouldBindJSON(f)
//fmt.Println(f) //fmt.Println(f)
if err != nil { if err != nil {
loginLimiter.RecordFailedAttempt(clientIp)
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), c.ClientIP())) global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), c.ClientIP()))
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return return
@@ -42,6 +48,7 @@ func (l *Login) Login(c *gin.Context) {
errList := global.Validator.ValidStruct(c, f) errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 { if len(errList) > 0 {
loginLimiter.RecordFailedAttempt(clientIp)
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), c.ClientIP())) global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), c.ClientIP()))
response.Error(c, errList[0]) response.Error(c, errList[0])
return return
@@ -50,6 +57,7 @@ func (l *Login) Login(c *gin.Context) {
u := service.AllService.UserService.InfoByUsernamePassword(f.Username, f.Password) u := service.AllService.UserService.InfoByUsernamePassword(f.Username, f.Password)
if u.Id == 0 { if u.Id == 0 {
loginLimiter.RecordFailedAttempt(clientIp)
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "UsernameOrPasswordError", c.RemoteIP(), c.ClientIP())) global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "UsernameOrPasswordError", c.RemoteIP(), c.ClientIP()))
response.Error(c, response.TranslateMsg(c, "UsernameOrPasswordError")) response.Error(c, response.TranslateMsg(c, "UsernameOrPasswordError"))
return return
@@ -60,7 +68,7 @@ func (l *Login) Login(c *gin.Context) {
return return
} }
//根据refer判断是webclient还是app // determine whether it is webclient or app based on refer
ref := c.GetHeader("referer") ref := c.GetHeader("referer")
if ref != "" { if ref != "" {
f.DeviceInfo.Type = model.LoginLogClientWeb f.DeviceInfo.Type = model.LoginLogClientWeb
@@ -84,9 +92,9 @@ func (l *Login) Login(c *gin.Context) {
} }
// LoginOptions // LoginOptions
// @Tags 登录 // @Tags login
// @Summary 登录选项 // @Summary login options
// @Description 登录选项 // @Description login options
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} []string // @Success 200 {object} []string
@@ -115,9 +123,9 @@ func (l *Login) LoginOptions(c *gin.Context) {
} }
// Logout // Logout
// @Tags 登录 // @Tags login
// @Summary 登出 // @Summary logout
// @Description 登出 // @Description logout
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {string} string // @Success 200 {string} string
+75 -35
View File
@@ -1,6 +1,8 @@
package api package api
import ( import (
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global" "github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/api" "github.com/lejianwen/rustdesk-api/v2/http/request/api"
@@ -8,7 +10,8 @@ import (
apiResp "github.com/lejianwen/rustdesk-api/v2/http/response/api" apiResp "github.com/lejianwen/rustdesk-api/v2/http/response/api"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service" "github.com/lejianwen/rustdesk-api/v2/service"
"net/http" "github.com/lejianwen/rustdesk-api/v2/utils"
"github.com/nicksnyder/go-i18n/v2/i18n"
) )
type Oauth struct { type Oauth struct {
@@ -62,36 +65,37 @@ func (o *Oauth) OidcAuthQueryPre(c *gin.Context) (*model.User, *model.UserToken)
var ut *model.UserToken var ut *model.UserToken
q := &api.OidcAuthQuery{} q := &api.OidcAuthQuery{}
// 解析查询参数并处理错误 // parse query parameters and handle errors
if err := c.ShouldBindQuery(q); err != nil { if err := c.ShouldBindQuery(q); err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+": "+err.Error()) response.Error(c, response.TranslateMsg(c, "ParamsError")+": "+err.Error())
return nil, nil return nil, nil
} }
// 获取 OAuth 缓存 // get OAuth cache
v := service.AllService.OauthService.GetOauthCache(q.Code) v := service.AllService.OauthService.GetOauthCache(q.Code)
if v == nil { if v == nil {
response.Error(c, response.TranslateMsg(c, "OauthExpired")) response.Error(c, response.TranslateMsg(c, "OauthExpired"))
return nil, nil return nil, nil
} }
// 如果 UserId 为 0,说明还在授权中 // if UserId is 0, authorization is still in progress
if v.UserId == 0 { if v.UserId == 0 {
c.JSON(http.StatusOK, gin.H{"message": "Authorization in progress, please login and bind"}) //fix: 1.4.2 webclient oidc
c.JSON(http.StatusOK, gin.H{"message": "Authorization in progress, please login and bind", "error": "No authed oidc is found"})
return nil, nil return nil, nil
} }
// 获取用户信息 // get user info
u = service.AllService.UserService.InfoById(v.UserId) u = service.AllService.UserService.InfoById(v.UserId)
if u == nil { if u == nil {
response.Error(c, response.TranslateMsg(c, "UserNotFound")) response.Error(c, response.TranslateMsg(c, "UserNotFound"))
return nil, nil return nil, nil
} }
// 删除 OAuth 缓存 // delete OAuth cache
service.AllService.OauthService.DeleteOauthCache(q.Code) service.AllService.OauthService.DeleteOauthCache(q.Code)
// 创建登录日志并生成用户令牌 // create a login log and generate a user token
ut = service.AllService.UserService.Login(u, &model.LoginLog{ ut = service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id, UserId: u.Id,
Client: v.DeviceType, Client: v.DeviceType,
@@ -107,7 +111,7 @@ func (o *Oauth) OidcAuthQueryPre(c *gin.Context) (*model.User, *model.UserToken)
return nil, nil return nil, nil
} }
// 返回用户令牌 // return user token
return u, ut return u, ut
} }
@@ -132,7 +136,7 @@ func (o *Oauth) OidcAuthQuery(c *gin.Context) {
}) })
} }
// OauthCallback 回调 // OauthCallback callback
// @Tags Oauth // @Tags Oauth
// @Summary OauthCallback // @Summary OauthCallback
// @Description OauthCallback // @Description OauthCallback
@@ -140,22 +144,23 @@ func (o *Oauth) OidcAuthQuery(c *gin.Context) {
// @Produce json // @Produce json
// @Success 200 {object} apiResp.LoginRes // @Success 200 {object} apiResp.LoginRes
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /oauth/callback [get] // @Router /oidc/callback [get]
func (o *Oauth) OauthCallback(c *gin.Context) { func (o *Oauth) OauthCallback(c *gin.Context) {
state := c.Query("state") state := c.Query("state")
if state == "" { if state == "" {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{ c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateParamMsg(c, "ParamIsEmpty", "state"), "message": "ParamIsEmpty",
"sub_message": "state",
}) })
return return
} }
cacheKey := state cacheKey := state
oauthService := service.AllService.OauthService oauthService := service.AllService.OauthService
//从缓存中获取 //get from cache
oauthCache := oauthService.GetOauthCache(cacheKey) oauthCache := oauthService.GetOauthCache(cacheKey)
if oauthCache == nil { if oauthCache == nil {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{ c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "OauthExpired"), "message": "OauthExpired",
}) })
return return
} }
@@ -164,12 +169,13 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
action := oauthCache.Action action := oauthCache.Action
verifier := oauthCache.Verifier verifier := oauthCache.Verifier
var user *model.User var user *model.User
// 获取用户信息 // get user info
code := c.Query("code") code := c.Query("code")
err, oauthUser := oauthService.Callback(code, verifier, op, nonce) err, oauthUser := oauthService.Callback(code, verifier, op, nonce)
if err != nil { if err != nil {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{ c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "OauthFailed") + response.TranslateMsg(c, err.Error()), "message": "OauthFailed",
"sub_message": err.Error(),
}) })
return return
} }
@@ -178,40 +184,40 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
if action == service.OauthActionTypeBind { if action == service.OauthActionTypeBind {
//fmt.Println("bind", ty, userData) //fmt.Println("bind", ty, userData)
// 检查此openid是否已经绑定过 // check whether this openid has already been bound
utr := oauthService.UserThirdInfo(op, openid) utr := oauthService.UserThirdInfo(op, openid)
if utr.UserId > 0 { if utr.UserId > 0 {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{ c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "OauthHasBindOtherUser"), "message": "OauthHasBindOtherUser",
}) })
return return
} }
//绑定 //bind
user = service.AllService.UserService.InfoById(userId) user = service.AllService.UserService.InfoById(userId)
if user == nil { if user == nil {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{ c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "ItemNotFound"), "message": "ItemNotFound",
}) })
return return
} }
//绑定 //bind
err := oauthService.BindOauthUser(userId, oauthUser, op) err := oauthService.BindOauthUser(userId, oauthUser, op)
if err != nil { if err != nil {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{ c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "BindFail"), "message": "BindFail",
}) })
return return
} }
c.HTML(http.StatusOK, "oauth_success.html", gin.H{ c.HTML(http.StatusOK, "oauth_success.html", gin.H{
"message": response.TranslateMsg(c, "BindSuccess"), "message": "BindSuccess",
}) })
return return
} else if action == service.OauthActionTypeLogin { } else if action == service.OauthActionTypeLogin {
//登录 //login
if userId != 0 { if userId != 0 {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{ c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "OauthHasBeenSuccess"), "message": "OauthHasBeenSuccess",
}) })
return return
} }
@@ -219,25 +225,24 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
if user == nil { if user == nil {
oauthConfig := oauthService.InfoByOp(op) oauthConfig := oauthService.InfoByOp(op)
if !*oauthConfig.AutoRegister { if !*oauthConfig.AutoRegister {
//c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定") //c.String(http.StatusInternalServerError, "not yet bound to a user, please bind first")
oauthCache.UpdateFromOauthUser(oauthUser) oauthCache.UpdateFromOauthUser(oauthUser)
url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey c.Redirect(http.StatusFound, "/_admin/#/oauth/bind/"+cacheKey)
c.Redirect(http.StatusFound, url)
return return
} }
//自动注册 //auto register
err, user = service.AllService.UserService.RegisterByOauth(oauthUser, op) err, user = service.AllService.UserService.RegisterByOauth(oauthUser, op)
if err != nil { if err != nil {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{ c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, err.Error()), "message": err.Error(),
}) })
return return
} }
} }
oauthCache.UserId = user.Id oauthCache.UserId = user.Id
oauthService.SetOauthCache(cacheKey, oauthCache, 0) oauthService.SetOauthCache(cacheKey, oauthCache, 0)
// 如果是webadmin,登录成功后跳转到webadmin // if it is webadmin, after successful login redirect to webadmin
if oauthCache.DeviceType == model.LoginLogClientWebAdmin { if oauthCache.DeviceType == model.LoginLogClientWebAdmin {
/*service.AllService.UserService.Login(u, &model.LoginLog{ /*service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id, UserId: u.Id,
@@ -247,19 +252,54 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
Type: model.LoginLogTypeOauth, Type: model.LoginLogTypeOauth,
Platform: oauthService.DeviceOs, Platform: oauthService.DeviceOs,
})*/ })*/
url := global.Config.Rustdesk.ApiServer + "/_admin/#/" c.Redirect(http.StatusFound, "/_admin/#/")
c.Redirect(http.StatusFound, url)
return return
} }
c.HTML(http.StatusOK, "oauth_success.html", gin.H{ c.HTML(http.StatusOK, "oauth_success.html", gin.H{
"message": response.TranslateMsg(c, "OauthSuccess"), "message": "OauthSuccess",
}) })
return return
} else { } else {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{ c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "ParamsError"), "message": "ParamsError",
}) })
return return
} }
} }
type MessageParams struct {
Lang string `json:"lang" form:"lang"`
Title string `json:"title" form:"title"`
Msg string `json:"msg" form:"msg"`
}
func (o *Oauth) Message(c *gin.Context) {
mp := &MessageParams{}
if err := c.ShouldBindQuery(mp); err != nil {
return
}
localizer := global.Localizer(mp.Lang)
res := ""
if mp.Title != "" {
title, err := localizer.LocalizeMessage(&i18n.Message{
ID: mp.Title,
})
if err == nil {
res = utils.StringConcat(";title='", title, "';")
}
}
if mp.Msg != "" {
msg, err := localizer.LocalizeMessage(&i18n.Message{
ID: mp.Msg,
})
if err == nil {
res = utils.StringConcat(res, "msg = '", msg, "';")
}
}
//returnjscontent
c.Header("Content-Type", "application/javascript")
c.String(http.StatusOK, res)
}
+27 -9
View File
@@ -1,6 +1,7 @@
package api package api
import ( import (
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
requstform "github.com/lejianwen/rustdesk-api/v2/http/request/api" requstform "github.com/lejianwen/rustdesk-api/v2/http/request/api"
@@ -13,12 +14,12 @@ type Peer struct {
} }
// SysInfo // SysInfo
// @Tags 地址 // @Tags System
// @Summary 提交系统信息 // @Summary submit system info
// @Description 提交系统信息 // @Description submit system info
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body requstform.PeerForm true "系统信息表单" // @Param body body requstform.PeerForm true "system info form"
// @Success 200 {string} string "SYSINFO_UPDATED,ID_NOT_FOUND" // @Success 200 {string} string "SYSINFO_UPDATED,ID_NOT_FOUND"
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /sysinfo [post] // @Router /sysinfo [post]
@@ -33,7 +34,7 @@ func (p *Peer) SysInfo(c *gin.Context) {
pe := service.AllService.PeerService.FindById(f.Id) pe := service.AllService.PeerService.FindById(f.Id)
if pe.RowId == 0 { if pe.RowId == 0 {
pe = f.ToPeer() pe = f.ToPeer()
pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid) pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid, pe.Id)
err = service.AllService.PeerService.Create(pe) err = service.AllService.PeerService.Create(pe)
if err != nil { if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
@@ -41,7 +42,7 @@ func (p *Peer) SysInfo(c *gin.Context) {
} }
} else { } else {
if pe.UserId == 0 { if pe.UserId == 0 {
pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid) pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid, pe.Id)
} }
fpe.RowId = pe.RowId fpe.RowId = pe.RowId
fpe.UserId = pe.UserId fpe.UserId = pe.UserId
@@ -51,8 +52,25 @@ func (p *Peer) SysInfo(c *gin.Context) {
return return
} }
} }
//SYSINFO_UPDATED 上传成功 //SYSINFO_UPDATED upload successful
//ID_NOT_FOUND 下次心跳会上传 //ID_NOT_FOUND will be uploaded on the next heartbeat
//直接响应文本 //respond with text directly
c.String(http.StatusOK, "SYSINFO_UPDATED") c.String(http.StatusOK, "SYSINFO_UPDATED")
} }
// SysInfoVer
// @Tags System
// @Summary get system version info
// @Description get system version info
// @Accept json
// @Produce json
// @Success 200 {string} string ""
// @Failure 500 {object} response.ErrorResponse
// @Router /sysinfo_ver [post]
func (p *Peer) SysInfoVer(c *gin.Context) {
// read the resources/version file
v := service.AllService.AppService.GetAppVersion()
// add the start time so the client can conveniently upload info
v = fmt.Sprintf("%s\n%s", v, service.AllService.AppService.GetStartTime())
c.String(http.StatusOK, v)
}
+8 -8
View File
@@ -10,10 +10,10 @@ import (
type User struct { type User struct {
} }
// currentUser 当前用户 // currentUser current user
// @Tags 用户 // @Tags user
// @Summary 用户信息 // @Summary user info
// @Description 用户信息 // @Description user info
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} apiResp.UserPayload // @Success 200 {object} apiResp.UserPayload
@@ -26,10 +26,10 @@ type User struct {
// c.JSON(http.StatusOK, up) // c.JSON(http.StatusOK, up)
//} //}
// Info 用户信息 // Info user info
// @Tags 用户 // @Tags user
// @Summary 用户信息 // @Summary user info
// @Description 用户信息 // @Description user info
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} apiResp.UserPayload // @Success 200 {object} apiResp.UserPayload
+10 -10
View File
@@ -12,10 +12,10 @@ import (
type WebClient struct { type WebClient struct {
} }
// ServerConfig 服务配置 // ServerConfig service config
// @Tags WEBCLIENT // @Tags WEBCLIENT
// @Summary 服务配置 // @Summary service config
// @Description 服务配置,给webclient提供api-server // @Description service config, provides an api-server for the webclient
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
@@ -42,10 +42,10 @@ func (i *WebClient) ServerConfig(c *gin.Context) {
) )
} }
// SharedPeer 分享的peer // SharedPeer shared peer
// @Tags WEBCLIENT // @Tags WEBCLIENT
// @Summary 分享的peer // @Summary shared peer
// @Description 分享的peer // @Description shared peer
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
@@ -65,7 +65,7 @@ func (i *WebClient) SharedPeer(c *gin.Context) {
return return
} }
if sr.Expire != 0 { if sr.Expire != 0 {
//判断是否过期,created_at + expire > now //check whether expired,created_at + expire > now
ca := time.Time(sr.CreatedAt) ca := time.Time(sr.CreatedAt)
if ca.Add(time.Second * time.Duration(sr.Expire)).Before(time.Now()) { if ca.Add(time.Second * time.Duration(sr.Expire)).Before(time.Now()) {
response.Fail(c, 101, "share expired") response.Fail(c, 101, "share expired")
@@ -89,10 +89,10 @@ func (i *WebClient) SharedPeer(c *gin.Context) {
}) })
} }
// ServerConfigV2 服务配置 // ServerConfigV2 service config
// @Tags WEBCLIENT_V2 // @Tags WEBCLIENT_V2
// @Summary 服务配置 // @Summary service config
// @Description 服务配置,给webclient提供api-server // @Description service config, provides an api-server for the webclient
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
+2 -2
View File
@@ -25,7 +25,7 @@ func ApiInit() {
} }
if global.Config.Gin.Mode == gin.ReleaseMode { if global.Config.Gin.Mode == gin.ReleaseMode {
//修改gin Recovery日志 输出为logger的输出点 // redirect gin's Recovery log output to the logger's output
if global.Logger != nil { if global.Logger != nil {
gin.DefaultErrorWriter = global.Logger.WriterLevel(logrus.ErrorLevel) gin.DefaultErrorWriter = global.Logger.WriterLevel(logrus.ErrorLevel)
} }
@@ -33,7 +33,7 @@ func ApiInit() {
g.NoRoute(func(c *gin.Context) { g.NoRoute(func(c *gin.Context) {
c.String(http.StatusNotFound, "404 not found") c.String(http.StatusNotFound, "404 not found")
}) })
g.Use(middleware.Logger(), gin.Recovery()) g.Use(middleware.Logger(), middleware.Limiter(), gin.Recovery())
router.WebInit(g) router.WebInit(g)
router.Init(g) router.Init(g)
router.ApiInit(g) router.ApiInit(g)
+5 -5
View File
@@ -6,20 +6,20 @@ import (
"github.com/lejianwen/rustdesk-api/v2/service" "github.com/lejianwen/rustdesk-api/v2/service"
) )
// BackendUserAuth 后台权限验证中间件 // BackendUserAuth backend permission verification middleware
func BackendUserAuth() gin.HandlerFunc { func BackendUserAuth() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
//测试先关闭 //disabled for testing
token := c.GetHeader("api-token") token := c.GetHeader("api-token")
if token == "" { if token == "" {
response.Fail(c, 403, "请先登录") response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
c.Abort() c.Abort()
return return
} }
user, ut := service.AllService.UserService.InfoByAccessToken(token) user, ut := service.AllService.UserService.InfoByAccessToken(token)
if user.Id == 0 { if user.Id == 0 {
response.Fail(c, 403, "请先登录") response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
c.Abort() c.Abort()
return return
} }
@@ -34,7 +34,7 @@ func BackendUserAuth() gin.HandlerFunc {
c.Set("curUser", user) c.Set("curUser", user)
c.Set("token", token) c.Set("token", token)
//如果时间小于1天,token自动续期 // if the remaining time is less than 1 day, the token is auto-renewed
service.AllService.UserService.AutoRefreshAccessToken(ut) service.AllService.UserService.AutoRefreshAccessToken(ut)
c.Next() c.Next()
+1 -1
View File
@@ -12,7 +12,7 @@ func AdminPrivilege() gin.HandlerFunc {
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) { if !service.AllService.UserService.IsAdmin(u) {
response.Fail(c, 403, "无权限") response.Fail(c, 403, response.TranslateMsg(c, "NoAccess"))
c.Abort() c.Abort()
return return
} }
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"net/http" "net/http"
) )
// Cors 跨域 // Cors CORS
func Cors() gin.HandlerFunc { func Cors() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
origin := c.GetHeader("Origin") origin := c.GetHeader("Origin")
+7 -7
View File
@@ -9,21 +9,21 @@ import (
func JwtAuth() gin.HandlerFunc { func JwtAuth() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
//测试先关闭 //disabled for testing
token := c.GetHeader("api-token") token := c.GetHeader("api-token")
if token == "" { if token == "" {
response.Fail(c, 403, "请先登录") response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
c.Abort() c.Abort()
return return
} }
uid, err := global.Jwt.ParseToken(token) uid, err := global.Jwt.ParseToken(token)
if err != nil { if err != nil {
response.Fail(c, 403, "请先登录") response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
c.Abort() c.Abort()
return return
} }
if uid == 0 { if uid == 0 {
response.Fail(c, 403, "请先登录") response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
c.Abort() c.Abort()
return return
} }
@@ -31,15 +31,15 @@ func JwtAuth() gin.HandlerFunc {
user := service.AllService.UserService.InfoById(uid) user := service.AllService.UserService.InfoById(uid)
//user := &model.User{ //user := &model.User{
// Id: uid, // Id: uid,
// Username: "测试用户", // Username: "test user",
//} //}
if user.Id == 0 { if user.Id == 0 {
response.Fail(c, 403, "请先登录") response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
c.Abort() c.Abort()
return return
} }
if !service.AllService.UserService.CheckUserEnable(user) { if !service.AllService.UserService.CheckUserEnable(user) {
response.Fail(c, 101, "你已被禁用") response.Fail(c, 101, response.TranslateMsg(c, "Banned"))
c.Abort() c.Abort()
return return
} }
+22
View File
@@ -0,0 +1,22 @@
package middleware
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"net/http"
)
func Limiter() gin.HandlerFunc {
return func(c *gin.Context) {
loginLimiter := global.LoginLimiter
clientIp := c.ClientIP()
banned, _ := loginLimiter.CheckSecurityStatus(clientIp)
if banned {
response.Fail(c, http.StatusLocked, response.TranslateMsg(c, "Banned"))
c.Abort()
return
}
c.Next()
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// Logger 日志中间件 // Logger logging middleware
func Logger() gin.HandlerFunc { func Logger() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
global.Logger.WithFields( global.Logger.WithFields(
+5 -5
View File
@@ -9,7 +9,7 @@ import (
func RustAuth() gin.HandlerFunc { func RustAuth() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
//fmt.Println(c.Request.URL, c.Request.Header) //fmt.Println(c.Request.URL, c.Request.Header)
//获取HTTP_AUTHORIZATION //getHTTP_AUTHORIZATION
token := c.GetHeader("Authorization") token := c.GetHeader("Authorization")
if token == "" { if token == "" {
c.JSON(401, gin.H{ c.JSON(401, gin.H{
@@ -25,13 +25,13 @@ func RustAuth() gin.HandlerFunc {
c.Abort() c.Abort()
return return
} }
//提取token,格式是Bearer {token} //extracttoken, format is Bearer {token}
//这里只是简单的提取 //this is just a simple extraction
token = token[7:] token = token[7:]
//验证token // verify the token
//检查是否设置了jwt key //check whether it is set jwt key
if len(global.Jwt.Key) > 0 { if len(global.Jwt.Key) > 0 {
uid, _ := service.AllService.UserService.VerifyJWT(token) uid, _ := service.AllService.UserService.VerifyJWT(token)
if uid == 0 { if uid == 0 {
+3 -3
View File
@@ -27,7 +27,7 @@ type AddressBookForm struct {
} }
func (a AddressBookForm) ToAddressBook() *model.AddressBook { func (a AddressBookForm) ToAddressBook() *model.AddressBook {
//tags转换 //tagsconvert
tags, _ := json.Marshal(a.Tags) tags, _ := json.Marshal(a.Tags)
return &model.AddressBook{ return &model.AddressBook{
@@ -52,7 +52,7 @@ func (a AddressBookForm) ToAddressBook() *model.AddressBook {
} }
func (a AddressBookForm) ToAddressBooks() []*model.AddressBook { func (a AddressBookForm) ToAddressBooks() []*model.AddressBook {
//tags转换 //tagsconvert
tags, _ := json.Marshal(a.Tags) tags, _ := json.Marshal(a.Tags)
abs := make([]*model.AddressBook, 0, len(a.UserIds)) abs := make([]*model.AddressBook, 0, len(a.UserIds))
@@ -92,7 +92,7 @@ type AddressBookQuery struct {
type ShareByWebClientForm struct { type ShareByWebClientForm struct {
Id string `json:"id" validate:"required"` Id string `json:"id" validate:"required"`
PasswordType string `json:"password_type" validate:"required,oneof=once fixed"` //只能是once,fixed PasswordType string `json:"password_type" validate:"required,oneof=once fixed"` //can only be once,fixed
Password string `json:"password" validate:"required"` Password string `json:"password" validate:"required"`
Expire int64 `json:"expire"` Expire int64 `json:"expire"`
} }
+5 -4
View File
@@ -1,10 +1,11 @@
package admin package admin
type Login struct { type Login struct {
Username string `json:"username" validate:"required" label:"用户名"` Username string `json:"username" validate:"required" label:"username"`
Password string `json:"password,omitempty" validate:"required" label:"密码"` Password string `json:"password,omitempty" validate:"required" label:"password"`
Platform string `json:"platform" label:"平台"` Platform string `json:"platform" label:"platform"`
Captcha string `json:"captcha,omitempty" label:"验证码"` Captcha string `json:"captcha,omitempty" label:"captcha"`
CaptchaId string `json:"captcha_id,omitempty"`
} }
type LoginLogQuery struct { type LoginLogQuery struct {
-2
View File
@@ -22,7 +22,6 @@ type OauthForm struct {
Scopes string `json:"scopes" validate:"omitempty"` Scopes string `json:"scopes" validate:"omitempty"`
ClientId string `json:"client_id" validate:"required"` ClientId string `json:"client_id" validate:"required"`
ClientSecret string `json:"client_secret" validate:"required"` ClientSecret string `json:"client_secret" validate:"required"`
RedirectUrl string `json:"redirect_url" validate:"required"`
AutoRegister *bool `json:"auto_register"` AutoRegister *bool `json:"auto_register"`
PkceEnable *bool `json:"pkce_enable"` PkceEnable *bool `json:"pkce_enable"`
PkceMethod string `json:"pkce_method"` PkceMethod string `json:"pkce_method"`
@@ -34,7 +33,6 @@ func (of *OauthForm) ToOauth() *model.Oauth {
OauthType: of.OauthType, OauthType: of.OauthType,
ClientId: of.ClientId, ClientId: of.ClientId,
ClientSecret: of.ClientSecret, ClientSecret: of.ClientSecret,
RedirectUrl: of.RedirectUrl,
AutoRegister: of.AutoRegister, AutoRegister: of.AutoRegister,
Issuer: of.Issuer, Issuer: of.Issuer,
Scopes: of.Scopes, Scopes: of.Scopes,
+5
View File
@@ -13,6 +13,7 @@ type PeerForm struct {
Uuid string `json:"uuid"` Uuid string `json:"uuid"`
Version string `json:"version"` Version string `json:"version"`
GroupId uint `json:"group_id"` GroupId uint `json:"group_id"`
Alias string `json:"alias"`
} }
type PeerBatchDeleteForm struct { type PeerBatchDeleteForm struct {
@@ -32,6 +33,7 @@ func (f *PeerForm) ToPeer() *model.Peer {
Uuid: f.Uuid, Uuid: f.Uuid,
Version: f.Version, Version: f.Version,
GroupId: f.GroupId, GroupId: f.GroupId,
Alias: f.Alias,
} }
} }
@@ -41,6 +43,9 @@ type PeerQuery struct {
Id string `json:"id" form:"id"` Id string `json:"id" form:"id"`
Hostname string `json:"hostname" form:"hostname"` Hostname string `json:"hostname" form:"hostname"`
Uuids string `json:"uuids" form:"uuids"` Uuids string `json:"uuids" form:"uuids"`
Ip string `json:"ip" form:"ip"`
Username string `json:"username" form:"username"`
Alias string `json:"alias" form:"alias"`
} }
type SimpleDataQuery struct { type SimpleDataQuery struct {
+4 -1
View File
@@ -7,13 +7,14 @@ import (
type UserForm struct { type UserForm struct {
Id uint `json:"id"` Id uint `json:"id"`
Username string `json:"username" validate:"required,gte=2,lte=32"` Username string `json:"username" validate:"required,gte=2,lte=32"`
Email string `json:"email"` //validate:"required,email" email不强制 Email string `json:"email"` //validate:"required,email" emailnot enforced
//Password string `json:"password" validate:"required,gte=4,lte=20"` //Password string `json:"password" validate:"required,gte=4,lte=20"`
Nickname string `json:"nickname"` Nickname string `json:"nickname"`
Avatar string `json:"avatar"` Avatar string `json:"avatar"`
GroupId uint `json:"group_id" validate:"required"` GroupId uint `json:"group_id" validate:"required"`
IsAdmin *bool `json:"is_admin" ` IsAdmin *bool `json:"is_admin" `
Status model.StatusCode `json:"status" validate:"required,gte=0"` Status model.StatusCode `json:"status" validate:"required,gte=0"`
Remark string `json:"remark"`
} }
func (uf *UserForm) FromUser(user *model.User) *UserForm { func (uf *UserForm) FromUser(user *model.User) *UserForm {
@@ -25,6 +26,7 @@ func (uf *UserForm) FromUser(user *model.User) *UserForm {
uf.GroupId = user.GroupId uf.GroupId = user.GroupId
uf.IsAdmin = user.IsAdmin uf.IsAdmin = user.IsAdmin
uf.Status = user.Status uf.Status = user.Status
uf.Remark = user.Remark
return uf return uf
} }
func (uf *UserForm) ToUser() *model.User { func (uf *UserForm) ToUser() *model.User {
@@ -37,6 +39,7 @@ func (uf *UserForm) ToUser() *model.User {
user.GroupId = uf.GroupId user.GroupId = uf.GroupId
user.IsAdmin = uf.IsAdmin user.IsAdmin = uf.IsAdmin
user.Status = uf.Status user.Status = uf.Status
user.Remark = uf.Remark
return user return user
} }
+1 -1
View File
@@ -1,7 +1,7 @@
package api package api
type OidcAuthRequest struct { type OidcAuthRequest struct {
DeviceInfo DeviceInfoInLogin `json:"deviceInfo" label:"设备信息"` DeviceInfo DeviceInfoInLogin `json:"deviceInfo" label:"device info"`
Id string `json:"id" label:"id"` Id string `json:"id" label:"id"`
Op string `json:"op" label:"op"` Op string `json:"op" label:"op"`
Uuid string `json:"uuid" label:"uuid"` Uuid string `json:"uuid" label:"uuid"`
+1 -1
View File
@@ -36,7 +36,7 @@ func (pf *PeerForm) ToPeer() *model.Peer {
} }
} }
// PersonalAddressBookForm 个人地址簿表单 // PersonalAddressBookForm personal address book form
type PersonalAddressBookForm struct { type PersonalAddressBookForm struct {
model.AddressBook model.AddressBook
ForceAlwaysRelay string `json:"forceAlwaysRelay"` ForceAlwaysRelay string `json:"forceAlwaysRelay"`
+10 -10
View File
@@ -29,25 +29,25 @@ type DeviceInfoInLogin struct {
} }
type LoginForm struct { type LoginForm struct {
AutoLogin bool `json:"autoLogin" label:"自动登录"` AutoLogin bool `json:"autoLogin" label:"auto login"`
DeviceInfo DeviceInfoInLogin `json:"deviceInfo" label:"设备信息"` DeviceInfo DeviceInfoInLogin `json:"deviceInfo" label:"device info"`
Id string `json:"id" label:"id"` Id string `json:"id" label:"id"`
Type string `json:"type" label:"type"` Type string `json:"type" label:"type"`
Uuid string `json:"uuid" label:"uuid"` Uuid string `json:"uuid" label:"uuid"`
Username string `json:"username" validate:"required,gte=2,lte=32" label:"用户名"` Username string `json:"username" validate:"required,gte=2,lte=32" label:"username"`
Password string `json:"password,omitempty" validate:"gte=4,lte=32" label:"密码"` Password string `json:"password,omitempty" validate:"gte=4,lte=32" label:"password"`
} }
type UserListQuery struct { type UserListQuery struct {
Page uint `json:"page" form:"page" validate:"required" label:"页码"` Page uint `json:"page" form:"page" validate:"required" label:"page number"`
PageSize uint `json:"page_size" form:"page_size" validate:"required" label:"每页数量"` PageSize uint `json:"pageSize" form:"pageSize" validate:"required" label:"items per page"`
Status int `json:"status" form:"status" label:"状态"` Status int `json:"status" form:"status" label:"status"`
Accessible string `json:"accessible" form:"accessible"` Accessible string `json:"accessible" form:"accessible"`
} }
type PeerListQuery struct { type PeerListQuery struct {
Page uint `json:"page" form:"page" validate:"required" label:"页码"` Page uint `json:"page" form:"page" validate:"required" label:"page number"`
PageSize uint `json:"page_size" form:"page_size" validate:"required" label:"每页数量"` PageSize uint `json:"pageSize" form:"pageSize" validate:"required" label:"items per page"`
Status int `json:"status" form:"status" label:"状态"` Status int `json:"status" form:"status" label:"status"`
Accessible string `json:"accessible" form:"accessible"` Accessible string `json:"accessible" form:"accessible"`
} }
-12
View File
@@ -22,15 +22,3 @@ type UserOauthItem struct {
Op string `json:"op"` Op string `json:"op"`
Status int `json:"status"` Status int `json:"status"`
} }
type GroupUsersPayload struct {
Id uint `json:"id"`
Username string `json:"username"`
Status int `json:"status"`
}
func (g *GroupUsersPayload) FromUser(user *model.User) {
g.Id = user.Id
g.Username = user.Username
g.Status = 1
}
+2 -2
View File
@@ -22,7 +22,7 @@ type WebClientPeerInfoPayload struct {
func (wcpp *WebClientPeerPayload) FromAddressBook(a *model.AddressBook) { func (wcpp *WebClientPeerPayload) FromAddressBook(a *model.AddressBook) {
wcpp.ViewStyle = "shrink" wcpp.ViewStyle = "shrink"
//24小时前 //24 hours ago
wcpp.Tm = time.Now().Add(-time.Hour * 24).UnixNano() wcpp.Tm = time.Now().Add(-time.Hour * 24).UnixNano()
wcpp.Info = WebClientPeerInfoPayload{ wcpp.Info = WebClientPeerInfoPayload{
Username: a.Username, Username: a.Username,
@@ -34,7 +34,7 @@ func (wcpp *WebClientPeerPayload) FromAddressBook(a *model.AddressBook) {
func (wcpp *WebClientPeerPayload) FromShareRecord(sr *model.ShareRecord) { func (wcpp *WebClientPeerPayload) FromShareRecord(sr *model.ShareRecord) {
wcpp.ViewStyle = "shrink" wcpp.ViewStyle = "shrink"
//24小时前 //24 hours ago
wcpp.Tm = time.Now().UnixNano() wcpp.Tm = time.Now().UnixNano()
wcpp.Tmppwd = sr.Password wcpp.Tmppwd = sr.Password
wcpp.Info = WebClientPeerInfoPayload{ wcpp.Info = WebClientPeerInfoPayload{
+1 -1
View File
@@ -50,7 +50,7 @@ func Init(g *gin.Engine) {
RustdeskCmdBind(adg) RustdeskCmdBind(adg)
DeviceGroupBind(adg) DeviceGroupBind(adg)
//访问静态文件 //access static files
//g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload")) //g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload"))
} }
+12 -6
View File
@@ -18,7 +18,7 @@ func ApiInit(g *gin.Engine) {
if global.Config.App.ShowSwagger == 1 { if global.Config.App.ShowSwagger == 1 {
g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("api"))) g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("api")))
} }
// 加载 HTML 模板 // load HTML template
g.LoadHTMLGlob("resources/templates/*") g.LoadHTMLGlob("resources/templates/*")
frg := g.Group("/api") frg := g.Group("/api")
@@ -33,7 +33,7 @@ func ApiInit(g *gin.Engine) {
{ {
l := &api.Login{} l := &api.Login{}
// 如果返回oidc则可以通过oidc登录 // if oidc is returned, login via oidc is possible
frg.GET("/login-options", l.LoginOptions) frg.GET("/login-options", l.LoginOptions)
frg.POST("/login", l.Login) frg.POST("/login", l.Login)
@@ -48,11 +48,17 @@ func ApiInit(g *gin.Engine) {
//api/oauth/callback //api/oauth/callback
frg.GET("/oauth/callback", o.OauthCallback) frg.GET("/oauth/callback", o.OauthCallback)
frg.GET("/oauth/login", o.OauthCallback) frg.GET("/oauth/login", o.OauthCallback)
frg.GET("/oauth/msg", o.Message)
frg.GET("/oidc/callback", o.OauthCallback)
frg.GET("/oidc/login", o.OauthCallback)
frg.GET("/oidc/msg", o.Message)
} }
{ {
pe := &api.Peer{} pe := &api.Peer{}
//提交系统信息 //submit system info
frg.POST("/sysinfo", pe.SysInfo) frg.POST("/sysinfo", pe.SysInfo)
frg.POST("/sysinfo_ver", pe.SysInfoVer)
} }
if global.Config.App.WebClient == 1 { if global.Config.App.WebClient == 1 {
@@ -87,14 +93,14 @@ func ApiInit(g *gin.Engine) {
{ {
ab := &api.Ab{} ab := &api.Ab{}
//获取地址 //get address
frg.GET("/ab", ab.Ab) frg.GET("/ab", ab.Ab)
//更新地址 //update address
frg.POST("/ab", ab.UpAb) frg.POST("/ab", ab.UpAb)
} }
PersonalRoutes(frg) PersonalRoutes(frg)
//访问静态文件 //access static files
g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/public/upload")) g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/public/upload"))
} }
+2 -2
View File
@@ -10,7 +10,7 @@ type Handler interface {
Gc() error Gc() error
} }
// MaxTimeOut 最大超时时间 // MaxTimeOut maximum timeout
const ( const (
TypeMem = "memory" TypeMem = "memory"
@@ -49,7 +49,7 @@ func EncodeValue(value interface{}) (string, error) {
} }
func DecodeValue(value string, rtv interface{}) error { func DecodeValue(value string, rtv interface{}) error {
//判断rtv的类型是否是string,如果是string,直接赋值并返回 // check whether rtv's type is string; if so, assign directly and return
/*switch rtv.(type) { /*switch rtv.(type) {
case *string: case *string:
*(rtv.(*string)) = value *(rtv.(*string)) = value
+6 -6
View File
@@ -42,7 +42,7 @@ func TestFileCacheSet(t *testing.T) {
err := fc.Set("123", "ddd", 0) err := fc.Set("123", "ddd", 0)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
t.Fatalf("写入失败") t.Fatalf("write failed")
} }
} }
@@ -50,12 +50,12 @@ func TestFileCacheGet(t *testing.T) {
fc := New("file") fc := New("file")
err := fc.Set("123", "45156", 300) err := fc.Set("123", "45156", 300)
if err != nil { if err != nil {
t.Fatalf("写入失败") t.Fatalf("write failed")
} }
res := "" res := ""
err = fc.Get("123", &res) err = fc.Get("123", &res)
if err != nil { if err != nil {
t.Fatalf("读取失败") t.Fatalf("read failed")
} }
fmt.Println("res", res) fmt.Println("res", res)
} }
@@ -69,7 +69,7 @@ func TestRedisCacheSet(t *testing.T) {
err := rc.Set("123", "ddd", 0) err := rc.Set("123", "ddd", 0)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
t.Fatalf("写入失败") t.Fatalf("write failed")
} }
} }
@@ -81,12 +81,12 @@ func TestRedisCacheGet(t *testing.T) {
}) })
err := rc.Set("123", "451156", 300) err := rc.Set("123", "451156", 300)
if err != nil { if err != nil {
t.Fatalf("写入失败") t.Fatalf("write failed")
} }
res := "" res := ""
err = rc.Get("123", &res) err = rc.Get("123", &res)
if err != nil { if err != nil {
t.Fatalf("读取失败") t.Fatalf("read failed")
} }
fmt.Println("res", res) fmt.Println("res", res)
} }
+4 -4
View File
@@ -32,12 +32,12 @@ func (c *FileCache) Get(key string, value interface{}) error {
return err return err
} }
// 获取值,如果文件不存在或者过期,返回空,过滤掉错误 // get value; if the file does not exist or has expired, return empty and ignore errors
func (c *FileCache) getValue(key string) (string, error) { func (c *FileCache) getValue(key string) (string, error) {
f := c.fileName(key) f := c.fileName(key)
fileInfo, err := os.Stat(f) fileInfo, err := os.Stat(f)
if err != nil { if err != nil {
//文件不存在 // file does not exist
return "", nil return "", nil
} }
difT := time.Now().Sub(fileInfo.ModTime()) difT := time.Now().Sub(fileInfo.ModTime())
@@ -52,7 +52,7 @@ func (c *FileCache) getValue(key string) (string, error) {
return string(data), nil return string(data), nil
} }
// 保存值 // save value
func (c *FileCache) saveValue(key string, value string, exp int) error { func (c *FileCache) saveValue(key string, value string, exp int) error {
f := c.fileName(key) f := c.fileName(key)
lock := c.getLock(f) lock := c.getLock(f)
@@ -91,7 +91,7 @@ func (c *FileCache) fileName(key string) string {
} }
func (c *FileCache) Gc() error { func (c *FileCache) Gc() error {
//检查文件过期时间,并删除 // check file expiration time and delete
return nil return nil
} }
+7 -7
View File
@@ -11,7 +11,7 @@ func TestFileSet(t *testing.T) {
err := fc.Set("123", "ddd", 0) err := fc.Set("123", "ddd", 0)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
t.Fatalf("写入失败") t.Fatalf("write failed")
} }
} }
@@ -21,7 +21,7 @@ func TestFileGet(t *testing.T) {
err := fc.Get("123", &res) err := fc.Get("123", &res)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
t.Fatalf("读取失败") t.Fatalf("read failed")
} }
fmt.Println("res", res) fmt.Println("res", res)
} }
@@ -32,7 +32,7 @@ func TestFileSetGet(t *testing.T) {
err = fc.Get("key1", &res) err = fc.Get("key1", &res)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
t.Fatalf("读取失败") t.Fatalf("read failed")
} }
fmt.Println("res", res) fmt.Println("res", res)
} }
@@ -46,7 +46,7 @@ func TestFileGetJson(t *testing.T) {
err2 := fc.Get("123", res) err2 := fc.Get("123", res)
fmt.Println("res", res) fmt.Println("res", res)
if err2 != nil { if err2 != nil {
t.Fatalf("读取失败" + err2.Error()) t.Fatalf("read failed" + err2.Error())
} }
} }
func TestFileSetGetJson(t *testing.T) { func TestFileSetGetJson(t *testing.T) {
@@ -59,7 +59,7 @@ func TestFileSetGetJson(t *testing.T) {
} }
err := fc.Set("123", old, 300) err := fc.Set("123", old, 300)
if err != nil { if err != nil {
t.Fatalf("写入失败") t.Fatalf("write failed")
} }
//old_rr.AA = "aaa" //old_rr.AA = "aaa"
fmt.Println("old_rr", old) fmt.Println("old_rr", old)
@@ -68,10 +68,10 @@ func TestFileSetGetJson(t *testing.T) {
err2 := fc.Get("123", res) err2 := fc.Get("123", res)
fmt.Println("res", res) fmt.Println("res", res)
if err2 != nil { if err2 != nil {
t.Fatalf("读取失败" + err2.Error()) t.Fatalf("read failed" + err2.Error())
} }
if !reflect.DeepEqual(res, old) { if !reflect.DeepEqual(res, old) {
t.Fatalf("读取错误") t.Fatalf("read error")
} }
} }
+9 -9
View File
@@ -11,8 +11,8 @@ import (
type MemoryCache struct { type MemoryCache struct {
data map[string]*CacheItem data map[string]*CacheItem
ll *list.List // 用于实现LRU ll *list.List // used to implement LRU
pq PriorityQueue // 用于实现TTL pq PriorityQueue // used to implement TTL
quit chan struct{} quit chan struct{}
mu sync.Mutex mu sync.Mutex
maxBytes int64 maxBytes int64
@@ -58,12 +58,12 @@ func (pq *PriorityQueue) Pop() interface{} {
} }
func (m *MemoryCache) Get(key string, value interface{}) error { func (m *MemoryCache) Get(key string, value interface{}) error {
// 使用反射将存储的值设置到传入的指针变量中 // use reflection to set the stored value into the passed-in pointer variable
val := reflect.ValueOf(value) val := reflect.ValueOf(value)
if val.Kind() != reflect.Ptr { if val.Kind() != reflect.Ptr {
return errors.New("value must be a pointer") return errors.New("value must be a pointer")
} }
//设为空值 // set to zero value
val.Elem().Set(reflect.Zero(val.Elem().Type())) val.Elem().Set(reflect.Zero(val.Elem().Type()))
m.mu.Lock() m.mu.Lock()
@@ -78,7 +78,7 @@ func (m *MemoryCache) Get(key string, value interface{}) error {
m.deleteItem(item) m.deleteItem(item)
return nil return nil
} }
//移动到队列尾部 // move to the tail of the queue
m.ll.MoveToBack(item.ListEle) m.ll.MoveToBack(item.ListEle)
err := DecodeValue(item.Value, value) err := DecodeValue(item.Value, value)
@@ -97,11 +97,11 @@ func (m *MemoryCache) Set(key string, value interface{}, exp int) error {
if err != nil { if err != nil {
return err return err
} }
//key 所占用的内存 // memory occupied by the key
keyBytes := int64(len(key)) keyBytes := int64(len(key))
//value所占用的内存空间大小 // size of memory occupied by the value
valueBytes := int64(len(v)) valueBytes := int64(len(v))
//判断是否超过最大内存限制 // check whether the maximum memory limit is exceeded
if m.maxBytes != 0 && m.maxBytes < keyBytes+valueBytes { if m.maxBytes != 0 && m.maxBytes < keyBytes+valueBytes {
return errors.New("exceed maxBytes") return errors.New("exceed maxBytes")
} }
@@ -176,7 +176,7 @@ func (m *MemoryCache) startEviction() {
}() }()
} }
// stopEviction 停止定时清理 // stopEviction stops the scheduled cleanup
func (m *MemoryCache) stopEviction() { func (m *MemoryCache) stopEviction() {
close(m.quit) close(m.quit)
} }
+14 -14
View File
@@ -11,7 +11,7 @@ func TestMemorySet(t *testing.T) {
err := mc.Set("123", "44567", 0) err := mc.Set("123", "44567", 0)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
t.Fatalf("写入失败") t.Fatalf("write failed")
} }
} }
@@ -22,10 +22,10 @@ func TestMemoryGet(t *testing.T) {
err := mc.Get("123", &res) err := mc.Get("123", &res)
fmt.Println("res", res) fmt.Println("res", res)
if err != nil { if err != nil {
t.Fatalf("读取失败 " + err.Error()) t.Fatalf("read failed " + err.Error())
} }
if res != "44567" { if res != "44567" {
t.Fatalf("读取错误") t.Fatalf("read error")
} }
} }
@@ -37,30 +37,30 @@ func TestMemorySetExpGet(t *testing.T) {
mc.Set("2", "5", 5) mc.Set("2", "5", 5)
err := mc.Set("3", "3", 3) err := mc.Set("3", "3", 3)
if err != nil { if err != nil {
t.Fatalf("写入失败") t.Fatalf("write failed")
} }
res := "" res := ""
err = mc.Get("3", &res) err = mc.Get("3", &res)
if err != nil { if err != nil {
t.Fatalf("读取失败" + err.Error()) t.Fatalf("read failed" + err.Error())
} }
fmt.Println("res 3", res) fmt.Println("res 3", res)
time.Sleep(4 * time.Second) time.Sleep(4 * time.Second)
//res = "" //res = ""
err = mc.Get("3", &res) err = mc.Get("3", &res)
if err != nil { if err != nil {
t.Fatalf("读取失败" + err.Error()) t.Fatalf("read failed" + err.Error())
} }
fmt.Println("res 3", res) fmt.Println("res 3", res)
err = mc.Get("2", &res) err = mc.Get("2", &res)
if err != nil { if err != nil {
t.Fatalf("读取失败" + err.Error()) t.Fatalf("read failed" + err.Error())
} }
fmt.Println("res 2", res) fmt.Println("res 2", res)
err = mc.Get("1", &res) err = mc.Get("1", &res)
if err != nil { if err != nil {
t.Fatalf("读取失败" + err.Error()) t.Fatalf("read failed" + err.Error())
} }
fmt.Println("res 1", res) fmt.Println("res 1", res)
@@ -69,29 +69,29 @@ func TestMemoryLru(t *testing.T) {
mc := NewMemoryCache(18) mc := NewMemoryCache(18)
mc.Set("1", "1111", 10) mc.Set("1", "1111", 10)
mc.Set("2", "2222", 5) mc.Set("2", "2222", 5)
//读取一次,2就会被放到最后 // read once, so 2 will be moved to the end
mc.Get("1", nil) mc.Get("1", nil)
err := mc.Set("3", "", 3) err := mc.Set("3", "three", 3)
if err != nil { if err != nil {
//t.Fatalf("写入失败") //t.Fatalf("write failed")
} }
res := "" res := ""
err = mc.Get("3", &res) err = mc.Get("3", &res)
if err != nil { if err != nil {
t.Fatalf("读取失败" + err.Error()) t.Fatalf("read failed" + err.Error())
} }
fmt.Println("res3", res) fmt.Println("res3", res)
res = "" res = ""
err = mc.Get("2", &res) err = mc.Get("2", &res)
if err != nil { if err != nil {
t.Fatalf("读取失败" + err.Error()) t.Fatalf("read failed" + err.Error())
} }
fmt.Println("res2", res) fmt.Println("res2", res)
res = "" res = ""
err = mc.Get("1", &res) err = mc.Get("1", &res)
if err != nil { if err != nil {
t.Fatalf("读取失败" + err.Error()) t.Fatalf("read failed" + err.Error())
} }
fmt.Println("res1", res) fmt.Println("res1", res)
+6 -6
View File
@@ -17,7 +17,7 @@ func TestRedisSet(t *testing.T) {
err := rc.Set("123", "ddd", 0) err := rc.Set("123", "ddd", 0)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
t.Fatalf("写入失败") t.Fatalf("write failed")
} }
} }
@@ -29,12 +29,12 @@ func TestRedisGet(t *testing.T) {
}) })
err := rc.Set("123", "451156", 300) err := rc.Set("123", "451156", 300)
if err != nil { if err != nil {
t.Fatalf("写入失败") t.Fatalf("write failed")
} }
res := "" res := ""
err = rc.Get("123", &res) err = rc.Get("123", &res)
if err != nil { if err != nil {
t.Fatalf("读取失败") t.Fatalf("read failed")
} }
fmt.Println("res", res) fmt.Println("res", res)
} }
@@ -54,16 +54,16 @@ func TestRedisGetJson(t *testing.T) {
} }
err := rc.Set("1233", old, 300) err := rc.Set("1233", old, 300)
if err != nil { if err != nil {
t.Fatalf("写入失败") t.Fatalf("write failed")
} }
res := &r{} res := &r{}
err2 := rc.Get("1233", res) err2 := rc.Get("1233", res)
if err2 != nil { if err2 != nil {
t.Fatalf("读取失败") t.Fatalf("read failed")
} }
if !reflect.DeepEqual(res, old) { if !reflect.DeepEqual(res, old) {
t.Fatalf("读取错误") t.Fatalf("read error")
} }
fmt.Println(res, res.Aa) fmt.Println(res, res.Aa)
} }
+5 -5
View File
@@ -6,7 +6,7 @@ import (
"sync" "sync"
) )
// 此处实现了一个简单的缓存,用于测试 // implements a simple cache used for testing
// SimpleCache is a simple cache implementation // SimpleCache is a simple cache implementation
type SimpleCache struct { type SimpleCache struct {
data map[string]interface{} data map[string]interface{}
@@ -19,21 +19,21 @@ func (s *SimpleCache) Get(key string, value interface{}) error {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
// 使用反射将存储的值设置到传入的指针变量中 // use reflection to set the stored value into the passed-in pointer variable
val := reflect.ValueOf(value) val := reflect.ValueOf(value)
if val.Kind() != reflect.Ptr { if val.Kind() != reflect.Ptr {
return errors.New("value must be a pointer") return errors.New("value must be a pointer")
} }
v, ok := s.data[key] v, ok := s.data[key]
if !ok { if !ok {
//设为空值 // set to zero value
val.Elem().Set(reflect.Zero(val.Elem().Type())) val.Elem().Set(reflect.Zero(val.Elem().Type()))
return nil return nil
} }
vval := reflect.ValueOf(v) vval := reflect.ValueOf(v)
if val.Elem().Type() != vval.Type() { if val.Elem().Type() != vval.Type() {
//设为空值 // set to zero value
val.Elem().Set(reflect.Zero(val.Elem().Type())) val.Elem().Set(reflect.Zero(val.Elem().Type()))
return nil return nil
} }
@@ -45,7 +45,7 @@ func (s *SimpleCache) Get(key string, value interface{}) error {
func (s *SimpleCache) Set(key string, value interface{}, exp int) error { func (s *SimpleCache) Set(key string, value interface{}, exp int) error {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
// 检查传入的值是否是指针,如果是则取其值 // check whether the passed-in value is a pointer; if so, dereference it
val := reflect.ValueOf(value) val := reflect.ValueOf(value)
if val.Kind() == reflect.Ptr { if val.Kind() == reflect.Ptr {
val = val.Elem() val = val.Elem()
+11 -11
View File
@@ -9,11 +9,11 @@ func TestSimpleCache_Set(t *testing.T) {
s := NewSimpleCache() s := NewSimpleCache()
err := s.Set("key", "value", 0) err := s.Set("key", "value", 0)
if err != nil { if err != nil {
t.Fatalf("写入失败") t.Fatalf("write failed")
} }
err = s.Set("key", 111, 0) err = s.Set("key", 111, 0)
if err != nil { if err != nil {
t.Fatalf("写入失败") t.Fatalf("write failed")
} }
} }
@@ -24,7 +24,7 @@ func TestSimpleCache_Get(t *testing.T) {
err = s.Get("key", &value) err = s.Get("key", &value)
fmt.Println("value", value) fmt.Println("value", value)
if err != nil { if err != nil {
t.Fatalf("读取失败") t.Fatalf("read failed")
} }
err = s.Set("key1", 11, 0) err = s.Set("key1", 11, 0)
@@ -32,7 +32,7 @@ func TestSimpleCache_Get(t *testing.T) {
err = s.Get("key1", &value1) err = s.Get("key1", &value1)
fmt.Println("value1", value1) fmt.Println("value1", value1)
if err != nil { if err != nil {
t.Fatalf("读取失败") t.Fatalf("read failed")
} }
err = s.Set("key2", []byte{'a', 'b'}, 0) err = s.Set("key2", []byte{'a', 'b'}, 0)
@@ -40,7 +40,7 @@ func TestSimpleCache_Get(t *testing.T) {
err = s.Get("key2", &value2) err = s.Get("key2", &value2)
fmt.Println("value2", string(value2)) fmt.Println("value2", string(value2))
if err != nil { if err != nil {
t.Fatalf("读取失败") t.Fatalf("read failed")
} }
err = s.Set("key3", 33.33, 0) err = s.Set("key3", 33.33, 0)
@@ -48,7 +48,7 @@ func TestSimpleCache_Get(t *testing.T) {
err = s.Get("key3", &value3) err = s.Get("key3", &value3)
fmt.Println("value3", value3) fmt.Println("value3", value3)
if err != nil { if err != nil {
t.Fatalf("读取失败") t.Fatalf("read failed")
} }
} }
@@ -80,18 +80,18 @@ func TestSimpleCache_GetStruct(t *testing.T) {
} }
err := s.Set("key", old, 300) err := s.Set("key", old, 300)
if err != nil { if err != nil {
t.Fatalf("写入失败") t.Fatalf("write failed")
} }
res := &r{} res := &r{}
err2 := s.Get("key", res) err2 := s.Get("key", res)
fmt.Println("res", res) fmt.Println("res", res)
if err2 != nil { if err2 != nil {
t.Fatalf("读取失败" + err2.Error()) t.Fatalf("read failed" + err2.Error())
} }
//修改原始值,看后面是否会变化 // modify the original value to see whether it changes afterward
old.A = "aa" old.A = "aa"
old_rr.AA = "aaa" old_rr.AA = "aaa"
fmt.Println("old", old) fmt.Println("old", old)
@@ -99,10 +99,10 @@ func TestSimpleCache_GetStruct(t *testing.T) {
err3 := s.Get("key", res2) err3 := s.Get("key", res2)
fmt.Println("res2", res2, res2.R.AA, res2.R.BB) fmt.Println("res2", res2, res2.R.AA, res2.R.BB)
if err3 != nil { if err3 != nil {
t.Fatalf("读取失败" + err3.Error()) t.Fatalf("read failed" + err3.Error())
} }
//if reflect.DeepEqual(res, old) { //if reflect.DeepEqual(res, old) {
// t.Fatalf("读取错误") // t.Fatalf("read error")
//} //}
} }
+6 -6
View File
@@ -34,30 +34,30 @@ Hj4yO4j5LOWDMTgDcLsZTxbGiTzkNc/HghrNIevDAQdgjJQNl84zDjyyCA4r/MA7
bYJTtYj8q6J0EDbRdT9b6hMclyzjNXdx2loJxR0R8WUeL1lDEPq8 bYJTtYj8q6J0EDbRdT9b6hMclyzjNXdx2loJxR0R8WUeL1lDEPq8
-----END RSA PRIVATE KEY-----` -----END RSA PRIVATE KEY-----`
// 测试token生成 // test token generation
func TestGenerateToken(t *testing.T) { func TestGenerateToken(t *testing.T) {
jwtService := NewJwt(pk, time.Second*1000) jwtService := NewJwt(pk, time.Second*1000)
token := jwtService.GenerateToken(1) token := jwtService.GenerateToken(1)
if token == "" { if token == "" {
t.Fatal("token生成失败") t.Fatal("token generation failed")
} }
fmt.Println(pk, token) fmt.Println(pk, token)
} }
// 测试token解析 // test token parsing
func TestParseToken(t *testing.T) { func TestParseToken(t *testing.T) {
jwtService := NewJwt(pk, time.Second*1000) jwtService := NewJwt(pk, time.Second*1000)
token := jwtService.GenerateToken(999) token := jwtService.GenerateToken(999)
if token == "" { if token == "" {
t.Fatal("token生成失败") t.Fatal("token generation failed")
} }
uid, err := jwtService.ParseToken(token) uid, err := jwtService.ParseToken(token)
if err != nil { if err != nil {
t.Fatal("token解析失败", err) t.Fatal("token parsing failed", err)
} }
if uid != 999 { if uid != 999 {
t.Fatal("token解析失败") t.Fatal("token parsing failed")
} }
} }
+1 -1
View File
@@ -27,7 +27,7 @@ func New(c *Config) *log.Logger {
//FieldsOrder: []string{"name", "age"}, //FieldsOrder: []string{"name", "age"},
}) })
// 日志文件 // log file
f := c.Path f := c.Path
var write io.Writer var write io.Writer
if f != "" { if f != "" {
+11 -12
View File
@@ -2,7 +2,6 @@ package orm
import ( import (
"fmt" "fmt"
"github.com/lejianwen/rustdesk-api/v2/global"
"gorm.io/driver/mysql" "gorm.io/driver/mysql"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
@@ -10,23 +9,23 @@ import (
) )
type MysqlConfig struct { type MysqlConfig struct {
Dns string Dsn string
MaxIdleConns int MaxIdleConns int
MaxOpenConns int MaxOpenConns int
} }
func NewMysql(mysqlConf *MysqlConfig) *gorm.DB { func NewMysql(mysqlConf *MysqlConfig, logwriter logger.Writer) *gorm.DB {
db, err := gorm.Open(mysql.New(mysql.Config{ db, err := gorm.Open(mysql.New(mysql.Config{
DSN: mysqlConf.Dns, // DSN data source name DSN: mysqlConf.Dsn, // DSN data source name
DefaultStringSize: 256, // string 类型字段的默认长度 DefaultStringSize: 256, // default length for string type fields
//DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 //DisableDatetimePrecision: true, // disable datetime precision; not supported by databases before MySQL 5.6
//DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 //DontSupportRenameIndex: true, // rename indexes by dropping and recreating them; databases before MySQL 5.7 and MariaDB do not support renaming indexes
//DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 //DontSupportRenameColumn: true, // rename columns using `change`; databases before MySQL 8 and MariaDB do not support renaming columns
//SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置 //SkipInitializeWithVersion: false, // auto-configure based on the current MySQL version
}), &gorm.Config{ }), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true, DisableForeignKeyConstraintWhenMigrating: true,
Logger: logger.New( Logger: logger.New(
global.Logger, // io writer logwriter, // io writer
logger.Config{ logger.Config{
SlowThreshold: time.Second, // Slow SQL threshold SlowThreshold: time.Second, // Slow SQL threshold
LogLevel: logger.Warn, // Log level LogLevel: logger.Warn, // Log level
@@ -43,10 +42,10 @@ func NewMysql(mysqlConf *MysqlConfig) *gorm.DB {
if err2 != nil { if err2 != nil {
fmt.Println(err2) fmt.Println(err2)
} }
// SetMaxIdleConns 设置空闲连接池中连接的最大数量 // SetMaxIdleConns sets the maximum number of connections in the idle connection pool
sqlDB.SetMaxIdleConns(mysqlConf.MaxIdleConns) sqlDB.SetMaxIdleConns(mysqlConf.MaxIdleConns)
// SetMaxOpenConns 设置打开数据库连接的最大数量。 // SetMaxOpenConns sets the maximum number of open database connections.
sqlDB.SetMaxOpenConns(mysqlConf.MaxOpenConns) sqlDB.SetMaxOpenConns(mysqlConf.MaxOpenConns)
return db return db
+45
View File
@@ -0,0 +1,45 @@
package orm
import (
"fmt"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"time"
)
type PostgresqlConfig struct {
Dsn string
MaxIdleConns int
MaxOpenConns int
}
func NewPostgresql(conf *PostgresqlConfig, logwriter logger.Writer) *gorm.DB {
db, err := gorm.Open(postgres.Open(conf.Dsn), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
Logger: logger.New(
logwriter, // io writer
logger.Config{
SlowThreshold: time.Second, // Slow SQL threshold
LogLevel: logger.Warn, // Log level
//IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
ParameterizedQueries: true, // Don't include params in the SQL log
Colorful: true,
},
),
})
if err != nil {
fmt.Println(err)
}
sqlDB, err2 := db.DB()
if err2 != nil {
fmt.Println(err2)
}
// SetMaxIdleConns sets the maximum number of connections in the idle connection pool
sqlDB.SetMaxIdleConns(conf.MaxIdleConns)
// SetMaxOpenConns sets the maximum number of open database connections.
sqlDB.SetMaxOpenConns(conf.MaxOpenConns)
return db
}
+4 -5
View File
@@ -2,7 +2,6 @@ package orm
import ( import (
"fmt" "fmt"
"github.com/lejianwen/rustdesk-api/v2/global"
"gorm.io/driver/sqlite" "gorm.io/driver/sqlite"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
@@ -14,11 +13,11 @@ type SqliteConfig struct {
MaxOpenConns int MaxOpenConns int
} }
func NewSqlite(sqliteConf *SqliteConfig) *gorm.DB { func NewSqlite(sqliteConf *SqliteConfig, logwriter logger.Writer) *gorm.DB {
db, err := gorm.Open(sqlite.Open("./data/rustdeskapi.db"), &gorm.Config{ db, err := gorm.Open(sqlite.Open("./data/rustdeskapi.db"), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true, DisableForeignKeyConstraintWhenMigrating: true,
Logger: logger.New( Logger: logger.New(
global.Logger, // io writer logwriter, // io writer
logger.Config{ logger.Config{
SlowThreshold: time.Second, // Slow SQL threshold SlowThreshold: time.Second, // Slow SQL threshold
LogLevel: logger.Warn, // Log level LogLevel: logger.Warn, // Log level
@@ -35,10 +34,10 @@ func NewSqlite(sqliteConf *SqliteConfig) *gorm.DB {
if err2 != nil { if err2 != nil {
fmt.Println(err2) fmt.Println(err2)
} }
// SetMaxIdleConns 设置空闲连接池中连接的最大数量 // SetMaxIdleConns sets the maximum number of connections in the idle connection pool
sqlDB.SetMaxIdleConns(sqliteConf.MaxIdleConns) sqlDB.SetMaxIdleConns(sqliteConf.MaxIdleConns)
// SetMaxOpenConns 设置打开数据库连接的最大数量。 // SetMaxOpenConns sets the maximum number of open database connections.
sqlDB.SetMaxOpenConns(sqliteConf.MaxOpenConns) sqlDB.SetMaxOpenConns(sqliteConf.MaxOpenConns)
return db return db
+5 -5
View File
@@ -240,11 +240,11 @@ func getMD5FromNewAuthString(r *http.Request) ([]byte, error) {
} }
/* VerifySignature /* VerifySignature
* VerifySignature需要三个重要的数据信息来进行签名验证: 1>获取公钥PublicKey; 2>生成新的MD5鉴权串; 3>解码Request携带的鉴权串; * VerifySignature needs three pieces of data to perform signature verification: 1> obtain the public key PublicKey; 2> generate a new MD5 authentication string; 3> decode the authentication string carried by the Request;
* 1>获取公钥PublicKey : 从RequestHeader的"x-oss-pub-key-url"字段中获取 URL, 读取URL链接的包含的公钥内容, 进行解码解析, 将其作为rsa.VerifyPKCS1v15的入参。 * 1> obtain the public key PublicKey: get the URL from the "x-oss-pub-key-url" field of the RequestHeader, read the public key content the URL points to, decode and parse it, and use it as the input parameter of rsa.VerifyPKCS1v15.
* 2>生成新的MD5鉴权串 : 把Request中的url中的path部分进行urldecode 加上url的query部分, 再加上body, 组合之后进行MD5编码, 得到MD5鉴权字节串。 * 2> generate a new MD5 authentication string: urldecode the path part of the url in the Request, append the query part of the url, then append the body, combine them and apply MD5 encoding to obtain the MD5 authentication byte string.
* 3>解码Request携带的鉴权串 获取RequestHeader的"authorization"字段, 对其进行Base64解码,作为签名验证的鉴权对比串。 * 3> decode the authentication string carried by the Request: get the "authorization" field of the RequestHeader and Base64-decode it to use as the comparison authentication string for signature verification.
* rsa.VerifyPKCS1v15进行签名验证,返回验证结果。 * rsa.VerifyPKCS1v15 performs the signature verification and returns the result.
* */ * */
func verifySignature(bytePublicKey []byte, byteMd5 []byte, authorization []byte) bool { func verifySignature(bytePublicKey []byte, byteMd5 []byte, authorization []byte) bool {
pubBlock, _ := pem.Decode(bytePublicKey) pubBlock, _ := pem.Decode(bytePublicKey)
+3 -3
View File
@@ -17,7 +17,7 @@ import "github.com/lejianwen/rustdesk-api/v2/model/custom_types"
// String loginName; //login username // String loginName; //login username
// bool? sameServer; // bool? sameServer;
// AddressBook 有些字段是Personal才会上传的 // AddressBook some fields are only uploaded for Personal
type AddressBook struct { type AddressBook struct {
RowId uint `gorm:"primaryKey" json:"row_id"` RowId uint `gorm:"primaryKey" json:"row_id"`
Id string `json:"id" gorm:"default:0;not null;index"` Id string `json:"id" gorm:"default:0;not null;index"`
@@ -59,8 +59,8 @@ type AddressBookCollectionRule struct {
IdModel IdModel
UserId uint `json:"user_id" gorm:"default:0;not null;"` UserId uint `json:"user_id" gorm:"default:0;not null;"`
CollectionId uint `json:"collection_id" gorm:"default:0;not null;index" validate:"required"` CollectionId uint `json:"collection_id" gorm:"default:0;not null;index" validate:"required"`
Rule int `json:"rule" gorm:"default:0;not null;" validate:"required,gte=1,lte=3"` // 0: 无 1: 读 2: 读写 3: 完全控制 Rule int `json:"rule" gorm:"default:0;not null;" validate:"required,gte=1,lte=3"` // 0: none 1: read 2: read-write 3: full control
Type int `json:"type" gorm:"default:1;not null;" validate:"required,gte=1,lte=2"` // 1: 个人 2: 群组 Type int `json:"type" gorm:"default:1;not null;" validate:"required,gte=1,lte=2"` // 1: personal 2: group
ToId uint `json:"to_id" gorm:"default:0;not null;" validate:"required,gt=0"` ToId uint `json:"to_id" gorm:"default:0;not null;" validate:"required,gt=0"`
TimeModel TimeModel
} }
+2 -2
View File
@@ -7,7 +7,7 @@ import (
"fmt" "fmt"
) )
// AutoJson 数据类型 // AutoJson data type
type AutoJson json.RawMessage type AutoJson json.RawMessage
func (j *AutoJson) Scan(value interface{}) error { func (j *AutoJson) Scan(value interface{}) error {
@@ -33,7 +33,7 @@ func (j *AutoJson) Scan(value interface{}) error {
} }
result := &json.RawMessage{} result := &json.RawMessage{}
err := json.Unmarshal(bytes, result) err := json.Unmarshal(bytes, result)
//解析json错误 返回空 // json parse error, return empty
if err != nil { if err != nil {
*j = AutoJson(json.RawMessage{'[', ']'}) *j = AutoJson(json.RawMessage{'[', ']'})
return nil return nil
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"time" "time"
) )
// AutoTime 自定义时间格式 // AutoTime custom time format
type AutoTime time.Time type AutoTime time.Time
func (mt AutoTime) Value() (driver.Value, error) { func (mt AutoTime) Value() (driver.Value, error) {

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