Compare commits

...

19 Commits

Author SHA1 Message Date
lejianwen
9b4fa679c2 add batch add ab from peer
add batch update ab tags
2024-12-06 19:45:41 +08:00
lejianwen
c2ae95c4cc up api docs 2024-12-06 10:36:40 +08:00
lejianwen
b2b7f60fd5 add batch delete user token 2024-12-06 10:36:27 +08:00
lejianwen
a465888b31 up username length to 32 #70 2024-12-06 10:22:28 +08:00
lejianwen
d368bdc84c up web client v2 2024-12-04 13:43:04 +08:00
lejianwen
cdc1150505 up readme 2024-11-28 12:42:15 +08:00
32d525c53c Create LICENSE 2024-11-26 17:29:46 +08:00
lejianwen
a89b40c607 add es lang 2024-11-26 10:43:01 +08:00
lejianwen
b6bd9150d9 up web client v2 from rustdesk 2024-11-22 19:54:19 +08:00
lejianwen
96e3e3bc86 up docs 2024-11-22 19:53:49 +08:00
lejianwen
41377f41bb Split the language 2024-11-22 19:39:28 +08:00
lejianwen
fb744f81e2 up img 2024-11-20 19:33:27 +08:00
lejianwen
750c3bcbcd fix #62 2024-11-20 19:32:44 +08:00
lejianwen
d4015d7284 fix 2024-11-20 09:20:29 +08:00
lejianwen
a9bf3fda73 fix https://github.com/lejianwen/rustdesk-api/discussions/59#discussioncomment-11306760 2024-11-20 09:17:29 +08:00
lejianwen
7f467a4814 up web client v2 2024-11-18 21:39:18 +08:00
lejianwen
9f10b5e983 up readme 2024-11-17 18:27:15 +08:00
lejianwen
5291270e6a up ws connect in https #12 2024-11-17 17:34:51 +08:00
lejianwen
56bba381d8 fix 2024-11-16 22:08:06 +08:00
27 changed files with 140835 additions and 143812 deletions

21
LICENSE Normal file
View File

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

View File

@@ -39,6 +39,7 @@
- 自动获取ID服务器和KEY - 自动获取ID服务器和KEY
- 自动获取地址簿 - 自动获取地址簿
- 游客通过临时分享链接直接远程到设备 - 游客通过临时分享链接直接远程到设备
- v2 Preview
- CLI - CLI
- 重置管理员密码 - 重置管理员密码
@@ -142,6 +143,12 @@
![webclient_conf](docs/webclient_conf.png) ![webclient_conf](docs/webclient_conf.png)
3. 登录后会自动同步ID服务器和KEY 3. 登录后会自动同步ID服务器和KEY
4. 登录后会将地址簿自动保存到web client中方便使用 4. 登录后会将地址簿自动保存到web client中方便使用
5. 现已支持`v2 Preview`,访问路径是`/webclient2`
![webclientv2](./docs/webclientv2.png)
6. `v2 preview` 部署
- 如果是通过`443`端口的`https`部署,必须配置反向代理,可以参考[官方文档](https://rustdesk.com/docs/en/self-host/rustdesk-server-pro/faq/#8-add-websocket-secure-wss-support-for-the-id-server-and-relay-server-to-enable-secure-communication-for-the-web-client)
- 如果是`http`或者其他的`https`端口部署,则和`v1`一样,配置好`21118`,`21119`即可
- 更多参考[Web-Client-V2-Preview-Document](https://github.com/lejianwen/rustdesk-api/wiki/Web-Client-V2-Preview-Document)
### 自动化文档: 使用 Swag 生成 API 文档,方便开发者理解和使用 API。 ### 自动化文档: 使用 Swag 生成 API 文档,方便开发者理解和使用 API。

View File

@@ -147,6 +147,13 @@ installation are `admin` `admin`, please change the password immediately.
![webclient_conf](docs/webclient_conf.png) ![webclient_conf](docs/webclient_conf.png)
3. After logging in, the ID server and key will be automatically synced. 3. After logging in, the ID server and key will be automatically synced.
4. The address book will also be automatically saved to the web client for convenient use. 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
- If deploying via `https` on port `443`, you must configure a reverse proxy. Refer to the [official documentation](https://rustdesk.com/docs/en/self-host/rustdesk-server-pro/faq/#8-add-websocket-secure-wss-support-for-the-id-server-and-relay-server-to-enable-secure-communication-for-the-web-client)
- If deploying via `http` or other `https` ports, configure `21118` and `21119` as with `v1`
- More [Web-Client-V2-Preview-Document](https://github.com/lejianwen/rustdesk-api/wiki/Web-Client-V2-Preview-Document)
### Automated Documentation : API documentation is generated using Swag, making it easier for developers to understand and use the API. ### Automated Documentation : API documentation is generated using Swag, making it easier for developers to understand and use the API.

View File

@@ -3569,7 +3569,7 @@ const docTemplateadmin = `{
"token": [] "token": []
} }
], ],
"description": "登录凭证删除", "description": "登录凭证批量删除",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -3579,7 +3579,7 @@ const docTemplateadmin = `{
"tags": [ "tags": [
"登录凭证" "登录凭证"
], ],
"summary": "登录凭证删除", "summary": "登录凭证批量删除",
"parameters": [ "parameters": [
{ {
"description": "登录凭证信息", "description": "登录凭证信息",
@@ -3587,7 +3587,7 @@ const docTemplateadmin = `{
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/model.UserToken" "$ref": "#/definitions/admin.UserTokenBatchDeleteForm"
} }
} }
], ],
@@ -3798,12 +3798,12 @@ const docTemplateadmin = `{
"properties": { "properties": {
"new_password": { "new_password": {
"type": "string", "type": "string",
"maxLength": 20, "maxLength": 32,
"minLength": 4 "minLength": 4
}, },
"old_password": { "old_password": {
"type": "string", "type": "string",
"maxLength": 20, "maxLength": 32,
"minLength": 4 "minLength": 4
} }
} }
@@ -4038,7 +4038,7 @@ const docTemplateadmin = `{
}, },
"username": { "username": {
"type": "string", "type": "string",
"maxLength": 10, "maxLength": 32,
"minLength": 2 "minLength": 2
} }
} }
@@ -4066,11 +4066,25 @@ const docTemplateadmin = `{
}, },
"password": { "password": {
"type": "string", "type": "string",
"maxLength": 20, "maxLength": 32,
"minLength": 4 "minLength": 4
} }
} }
}, },
"admin.UserTokenBatchDeleteForm": {
"type": "object",
"required": [
"ids"
],
"properties": {
"ids": {
"type": "array",
"items": {
"type": "integer"
}
}
}
},
"model.AddressBook": { "model.AddressBook": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -3562,7 +3562,7 @@
"token": [] "token": []
} }
], ],
"description": "登录凭证删除", "description": "登录凭证批量删除",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -3572,7 +3572,7 @@
"tags": [ "tags": [
"登录凭证" "登录凭证"
], ],
"summary": "登录凭证删除", "summary": "登录凭证批量删除",
"parameters": [ "parameters": [
{ {
"description": "登录凭证信息", "description": "登录凭证信息",
@@ -3580,7 +3580,7 @@
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/model.UserToken" "$ref": "#/definitions/admin.UserTokenBatchDeleteForm"
} }
} }
], ],
@@ -3791,12 +3791,12 @@
"properties": { "properties": {
"new_password": { "new_password": {
"type": "string", "type": "string",
"maxLength": 20, "maxLength": 32,
"minLength": 4 "minLength": 4
}, },
"old_password": { "old_password": {
"type": "string", "type": "string",
"maxLength": 20, "maxLength": 32,
"minLength": 4 "minLength": 4
} }
} }
@@ -4031,7 +4031,7 @@
}, },
"username": { "username": {
"type": "string", "type": "string",
"maxLength": 10, "maxLength": 32,
"minLength": 2 "minLength": 2
} }
} }
@@ -4059,11 +4059,25 @@
}, },
"password": { "password": {
"type": "string", "type": "string",
"maxLength": 20, "maxLength": 32,
"minLength": 4 "minLength": 4
} }
} }
}, },
"admin.UserTokenBatchDeleteForm": {
"type": "object",
"required": [
"ids"
],
"properties": {
"ids": {
"type": "array",
"items": {
"type": "integer"
}
}
}
},
"model.AddressBook": { "model.AddressBook": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -78,11 +78,11 @@ definitions:
admin.ChangeCurPasswordForm: admin.ChangeCurPasswordForm:
properties: properties:
new_password: new_password:
maxLength: 20 maxLength: 32
minLength: 4 minLength: 4
type: string type: string
old_password: old_password:
maxLength: 20 maxLength: 32
minLength: 4 minLength: 4
type: string type: string
required: required:
@@ -238,7 +238,7 @@ definitions:
- $ref: '#/definitions/model.StatusCode' - $ref: '#/definitions/model.StatusCode'
minimum: 0 minimum: 0
username: username:
maxLength: 10 maxLength: 32
minLength: 2 minLength: 2
type: string type: string
required: required:
@@ -258,13 +258,22 @@ definitions:
id: id:
type: integer type: integer
password: password:
maxLength: 20 maxLength: 32
minLength: 4 minLength: 4
type: string type: string
required: required:
- id - id
- password - password
type: object type: object
admin.UserTokenBatchDeleteForm:
properties:
ids:
items:
type: integer
type: array
required:
- ids
type: object
model.AddressBook: model.AddressBook:
properties: properties:
alias: alias:
@@ -2871,14 +2880,14 @@ paths:
post: post:
consumes: consumes:
- application/json - application/json
description: 登录凭证删除 description: 登录凭证批量删除
parameters: parameters:
- description: 登录凭证信息 - description: 登录凭证信息
in: body in: body
name: body name: body
required: true required: true
schema: schema:
$ref: '#/definitions/model.UserToken' $ref: '#/definitions/admin.UserTokenBatchDeleteForm'
produces: produces:
- application/json - application/json
responses: responses:
@@ -2892,7 +2901,7 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: 登录凭证删除 summary: 登录凭证批量删除
tags: tags:
- 登录凭证 - 登录凭证
/admin/user_token/list: /admin/user_token/list:

View File

@@ -1056,7 +1056,7 @@ const docTemplateapi = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"WEBCLIENT" "WEBCLIENT_V2"
], ],
"summary": "服务配置", "summary": "服务配置",
"responses": { "responses": {
@@ -1356,7 +1356,7 @@ const docTemplateapi = `{
}, },
"password": { "password": {
"type": "string", "type": "string",
"maxLength": 20, "maxLength": 32,
"minLength": 4 "minLength": 4
}, },
"type": { "type": {
@@ -1364,7 +1364,7 @@ const docTemplateapi = `{
}, },
"username": { "username": {
"type": "string", "type": "string",
"maxLength": 10, "maxLength": 32,
"minLength": 2 "minLength": 2
}, },
"uuid": { "uuid": {

View File

@@ -1049,7 +1049,7 @@
"application/json" "application/json"
], ],
"tags": [ "tags": [
"WEBCLIENT" "WEBCLIENT_V2"
], ],
"summary": "服务配置", "summary": "服务配置",
"responses": { "responses": {
@@ -1349,7 +1349,7 @@
}, },
"password": { "password": {
"type": "string", "type": "string",
"maxLength": 20, "maxLength": 32,
"minLength": 4 "minLength": 4
}, },
"type": { "type": {
@@ -1357,7 +1357,7 @@
}, },
"username": { "username": {
"type": "string", "type": "string",
"maxLength": 10, "maxLength": 32,
"minLength": 2 "minLength": 2
}, },
"uuid": { "uuid": {

View File

@@ -62,13 +62,13 @@ definitions:
id: id:
type: string type: string
password: password:
maxLength: 20 maxLength: 32
minLength: 4 minLength: 4
type: string type: string
type: type:
type: string type: string
username: username:
maxLength: 10 maxLength: 32
minLength: 2 minLength: 2
type: string type: string
uuid: uuid:
@@ -869,7 +869,7 @@ paths:
- token: [] - token: []
summary: 服务配置 summary: 服务配置
tags: tags:
- WEBCLIENT - WEBCLIENT_V2
/shared-peer: /shared-peer:
post: post:
consumes: consumes:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/webclientv2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -3,6 +3,7 @@ package global
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-playground/locales/en" "github.com/go-playground/locales/en"
"github.com/go-playground/locales/es"
"github.com/go-playground/locales/ko" "github.com/go-playground/locales/ko"
"github.com/go-playground/locales/ru" "github.com/go-playground/locales/ru"
"github.com/go-playground/locales/zh_Hans_CN" "github.com/go-playground/locales/zh_Hans_CN"
@@ -10,6 +11,7 @@ import (
ut "github.com/go-playground/universal-translator" ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
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"
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"
"reflect" "reflect"
@@ -23,13 +25,15 @@ func ApiInitValidator() {
cn := zh_Hans_CN.New() cn := zh_Hans_CN.New()
koT := ko.New() koT := ko.New()
ruT := ru.New() ruT := ru.New()
esT := es.New()
uni := ut.New(enT, cn, koT, ruT) uni := ut.New(enT, cn, koT, ruT, esT)
enTrans, _ := uni.GetTranslator("en") enTrans, _ := uni.GetTranslator("en")
zhTrans, _ := uni.GetTranslator("zh_Hans_CN") zhTrans, _ := uni.GetTranslator("zh_Hans_CN")
koTrans, _ := uni.GetTranslator("ko") koTrans, _ := uni.GetTranslator("ko")
ruTrans, _ := uni.GetTranslator("ru") ruTrans, _ := uni.GetTranslator("ru")
esTrans, _ := uni.GetTranslator("es")
err := zh_translations.RegisterDefaultTranslations(validate, zhTrans) err := zh_translations.RegisterDefaultTranslations(validate, zhTrans)
if err != nil { if err != nil {
@@ -49,6 +53,10 @@ func ApiInitValidator() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = es_translations.RegisterDefaultTranslations(validate, esTrans)
if err != nil {
panic(err)
}
validate.RegisterTagNameFunc(func(field reflect.StructField) string { validate.RegisterTagNameFunc(func(field reflect.StructField) string {
label := field.Tag.Get("label") label := field.Tag.Get("label")
@@ -115,6 +123,9 @@ func getTranslatorForLang(lang string) ut.Translator {
case "ru": case "ru":
trans, _ := Validator.UT.GetTranslator("ru") trans, _ := Validator.UT.GetTranslator("ru")
return trans return trans
case "es":
trans, _ := Validator.UT.GetTranslator("es")
return trans
case "en": case "en":
fallthrough fallthrough
default: default:

View File

@@ -6,6 +6,7 @@ import (
"Gwen/http/response" "Gwen/http/response"
"Gwen/model" "Gwen/model"
"Gwen/service" "Gwen/service"
"encoding/json"
_ "encoding/json" _ "encoding/json"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/gorm" "gorm.io/gorm"
@@ -327,3 +328,70 @@ func (ct *AddressBook) ShareByWebClient(c *gin.Context) {
"share_token": m.ShareToken, "share_token": m.ShareToken,
}) })
} }
func (ct *AddressBook) BatchCreateFromPeers(c *gin.Context) {
f := &admin.BatchCreateFromPeersForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
if f.CollectionId != 0 {
collection := service.AllService.AddressBookService.CollectionInfoById(f.CollectionId)
if collection.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if collection.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
}
peers := service.AllService.PeerService.List(1, 999, func(tx *gorm.DB) {
tx.Where("row_id in ?", f.PeerIds)
tx.Where("user_id = ?", u.Id)
})
if peers.Total == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
tags, _ := json.Marshal(f.Tags)
for _, peer := range peers.Peers {
ab := service.AllService.AddressBookService.FromPeer(peer)
ab.Tags = tags
ab.CollectionId = f.CollectionId
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(u.Id, ab.Id, ab.CollectionId)
if ex.RowId != 0 {
continue
}
service.AllService.AddressBookService.Create(ab)
}
response.Success(c, nil)
}
func (ct *AddressBook) BatchUpdateTags(c *gin.Context) {
f := &admin.BatchUpdateTagsForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
abs := service.AllService.AddressBookService.List(1, 999, func(tx *gorm.DB) {
tx.Where("row_id in ?", f.RowIds)
tx.Where("user_id = ?", u.Id)
})
if abs.Total == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.AddressBookService.BatchUpdateTags(abs.AddressBooks, f.Tags)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}

View File

@@ -81,3 +81,33 @@ func (ct *UserToken) Delete(c *gin.Context) {
} }
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
} }
// BatchDelete 批量删除
// @Tags 登录凭证
// @Summary 登录凭证批量删除
// @Description 登录凭证批量删除
// @Accept json
// @Produce json
// @Param body body admin.UserTokenBatchDeleteForm true "登录凭证信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/user_token/delete [post]
// @Security token
func (ct *UserToken) BatchDelete(c *gin.Context) {
f := &admin.UserTokenBatchDeleteForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
ids := f.Ids
if len(ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.UserService.BatchDeleteUserToken(ids)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
}

View File

@@ -94,7 +94,7 @@ func (i *WebClient) SharedPeer(c *gin.Context) {
// @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 /server-config [get] // @Router /server-config-v2 [get]
// @Security token // @Security token
func (i *WebClient) ServerConfigV2(c *gin.Context) { func (i *WebClient) ServerConfigV2(c *gin.Context) {
response.Success( response.Success(

View File

@@ -122,3 +122,13 @@ type AddressBookCollectionRuleQuery struct {
IsMy int `form:"is_my"` IsMy int `form:"is_my"`
PageQuery PageQuery
} }
type BatchCreateFromPeersForm struct {
CollectionId uint `json:"collection_id"`
PeerIds []uint `json:"peer_ids"`
Tags []string `json:"tags"`
}
type BatchUpdateTagsForm struct {
RowIds []uint `json:"row_ids"`
Tags []string `json:"tags"`
}

View File

@@ -6,7 +6,7 @@ import (
type UserForm struct { type UserForm struct {
Id uint `json:"id"` Id uint `json:"id"`
Username string `json:"username" validate:"required,gte=2,lte=10"` 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" email不强制
//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"`
@@ -51,12 +51,12 @@ type UserQuery struct {
} }
type UserPasswordForm struct { type UserPasswordForm struct {
Id uint `json:"id" validate:"required"` Id uint `json:"id" validate:"required"`
Password string `json:"password" validate:"required,gte=4,lte=20"` Password string `json:"password" validate:"required,gte=4,lte=32"`
} }
type ChangeCurPasswordForm struct { type ChangeCurPasswordForm struct {
OldPassword string `json:"old_password" validate:"required,gte=4,lte=20"` OldPassword string `json:"old_password" validate:"required,gte=4,lte=32"`
NewPassword string `json:"new_password" validate:"required,gte=4,lte=20"` NewPassword string `json:"new_password" validate:"required,gte=4,lte=32"`
} }
type GroupUsersQuery struct { type GroupUsersQuery struct {
IsMy int `json:"is_my"` IsMy int `json:"is_my"`
@@ -64,8 +64,12 @@ type GroupUsersQuery struct {
} }
type RegisterForm struct { type RegisterForm struct {
Username string `json:"username" validate:"required,gte=2,lte=10"` Username string `json:"username" validate:"required,gte=2,lte=32"`
Email string `json:"email"` // validate:"required,email" Email string `json:"email"` // validate:"required,email"
Password string `json:"password" validate:"required,gte=4,lte=20"` Password string `json:"password" validate:"required,gte=4,lte=32"`
ConfirmPassword string `json:"confirm_password" validate:"required,gte=4,lte=20"` ConfirmPassword string `json:"confirm_password" validate:"required,gte=4,lte=32"`
}
type UserTokenBatchDeleteForm struct {
Ids []uint `json:"ids" validate:"required"`
} }

View File

@@ -34,8 +34,8 @@ type LoginForm struct {
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=10" label:"用户名"` Username string `json:"username" validate:"required,gte=2,lte=32" label:"用户名"`
Password string `json:"password,omitempty" validate:"gte=4,lte=20" label:"密码"` Password string `json:"password,omitempty" validate:"gte=4,lte=32" label:"密码"`
} }
type UserListQuery struct { type UserListQuery struct {

View File

@@ -107,9 +107,12 @@ func AddressBookBind(rg *gin.RouterGroup) {
aR.POST("/update", cont.Update) aR.POST("/update", cont.Update)
aR.POST("/delete", cont.Delete) aR.POST("/delete", cont.Delete)
aR.POST("/shareByWebClient", cont.ShareByWebClient) aR.POST("/shareByWebClient", cont.ShareByWebClient)
aR.POST("/batchCreateFromPeers", cont.BatchCreateFromPeers)
aR.POST("/batchUpdateTags", cont.BatchUpdateTags)
arp := aR.Use(middleware.AdminPrivilege()) arp := aR.Use(middleware.AdminPrivilege())
arp.POST("/batchCreate", cont.BatchCreate) arp.POST("/batchCreate", cont.BatchCreate)
} }
} }
func PeerBind(rg *gin.RouterGroup) { func PeerBind(rg *gin.RouterGroup) {
@@ -195,6 +198,7 @@ func UserTokenBind(rg *gin.RouterGroup) {
cont := &admin.UserToken{} cont := &admin.UserToken{}
aR.GET("/list", cont.List) aR.GET("/list", cont.List)
aR.POST("/delete", cont.Delete) aR.POST("/delete", cont.Delete)
aR.POST("/batchDelete", cont.BatchDelete)
} }
func ConfigBind(rg *gin.RouterGroup) { func ConfigBind(rg *gin.RouterGroup) {
aR := rg.Group("/config") aR := rg.Group("/config")

134
resources/i18n/es.toml Normal file
View File

@@ -0,0 +1,134 @@
[Test]
description = "test"
one = "prueba1"
other = "Prueba2 {{.P0}}"
[ParamsError]
description = "Params validation failed."
one = "La validación de los parámetros falló."
other = "La validación de los parámetros falló."
[OperationFailed]
description = "OperationFailed."
one = "La operación falló."
other = "La operación falló."
[OperationSuccess]
description = "OperationSuccess."
one = "La operación fue exitosa."
other = "La operación fue exitosa."
[ItemExists]
description = "Item already exists."
one = "El elemento ya existe."
other = "El elemento ya existe."
[ItemNotFound]
description = "Item not found."
one = "El elemento no fue encontrado."
other = "El elemento no fue encontrado."
[NoAccess]
description = "No access."
one = "Sin acceso."
other = "Sin acceso."
[UsernameOrPasswordError]
description = "Username or password error."
one = "Error de usuario o contraseña."
other = "Error de usuario o contraseña."
[SystemError]
description = "System error."
one = "Error del sistema."
other = "Error del sistema."
[ConfigNotFound]
description = "Config not found."
one = "Configuración no encontrada."
other = "Configuración no encontrada."
[OauthExpired]
description = "Oauth expired."
one = "Oauth expirado, por favor intente nuevamente."
other = "Oauth expirado, por favor intente nuevamente."
[OauthFailed]
description = "Oauth failed."
one = "Oauth falló."
other = "Oauth falló."
[OauthHasBindOtherUser]
description = "Oauth has bind other user."
one = "Oauth está vinculado a otro usuario."
other = "Oauth está vinculado a otro usuario."
[ParamIsEmpty]
description = "Param is empty."
one = "{{.P0}} está vacío."
other = "{{.P0}} está vacío."
[BindFail]
description = "Bind fail."
one = "Fallo al vincular."
other = "Fallo al vincular."
[BindSuccess]
description = "Bind success."
one = "Vinculación exitosa."
other = "Vinculación exitosa."
[OauthHasBeenSuccess]
description = "Oauth has been success."
one = "Oauth fue exitoso."
other = "Oauth fue exitoso."
[OauthSuccess]
description = "Oauth success."
one = "Oauth exitoso."
other = "Oauth exitoso."
[OauthRegisterSuccess]
description = "Oauth register success."
one = "Registro de Oauth exitoso."
other = "Registro de Oauth exitoso."
[OauthRegisterFailed]
description = "Oauth register failed."
one = "Registro de Oauth falló."
other = "Registro de Oauth falló."
[GetOauthTokenError]
description = "Get oauth token error."
one = "Error al obtener el token de Oauth."
other = "Error al obtener el token de Oauth."
[GetOauthUserInfoError]
description = "Get oauth user info error."
one = "Error al obtener la información del usuario de Oauth."
other = "Error al obtener la información del usuario de Oauth."
[DecodeOauthUserInfoError]
description = "Decode oauth user info error."
one = "Error al decodificar la información del usuario de Oauth."
other = "Error al decodificar la información del usuario de Oauth."
[OldPasswordError]
description = "Old password error."
one = "Error con la contraseña anterior."
other = "Error con la contraseña anterior."
[DefaultGroup]
description = "Default group"
one = "Grupo predeterminado"
other = "Grupo predeterminado"
[ShareGroup]
description = "Share group"
one = "Grupo compartido"
other = "Grupo compartido"
[RegisterClosed]
description = "Register closed."
one = "Registro cerrado."
other = "Registro cerrado."

View File

@@ -32,7 +32,7 @@
<title>RustDesk</title> <title>RustDesk</title>
<script src="/webclient-config/index.js"></script> <script src="/webclient-config/index.js"></script>
<link rel="manifest" href="manifest.json" /> <link rel="manifest" href="manifest.json" />
<script type="module" crossorigin src="js/dist/index.js?v=3"></script> <script type="module" crossorigin src="js/dist/index.js?v=893935a2"></script>
<link rel="modulepreload" href="js/dist/vendor.js?v=0b990c6e" /> <link rel="modulepreload" href="js/dist/vendor.js?v=0b990c6e" />
<style> <style>
html, html,
@@ -259,7 +259,7 @@
} }
scriptLoaded = true; scriptLoaded = true;
var scriptTag = document.createElement("script"); var scriptTag = document.createElement("script");
scriptTag.src = "main.dart.js?v=6d46937e"; scriptTag.src = "main.dart.js?v=df360f45";
scriptTag.type = "application/javascript"; scriptTag.type = "application/javascript";
document.body.append(scriptTag); document.body.append(scriptTag);
} }

File diff suppressed because it is too large Load Diff

28163
resources/web2/js/dist/lang.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
window._gwen = {} window._gwen = {}
window._gwen.kv = {} window._gwen.kv = {}
const apiserver = localStorage.getItem('api-server') const storage_prefix = 'wc-'
const apiserver = localStorage.getItem('wc-api-server')
function stringToUint8Array(str) { function stringToUint8Array(str) {
var arr = []; var arr = [];
@@ -23,10 +24,10 @@ function getQueryVariable() {
getQueryVariable() getQueryVariable()
const id = window._gwen.kv.id || '' /*const id = window._gwen.kv.id || ''
if (id) { if (id) {
localStorage.setItem('remote-id', id) localStorage.setItem(storage_prefix+'option:local:last_remote_id', id)
} }*/
const share_token = window._gwen.kv.share_token || '' const share_token = window._gwen.kv.share_token || ''
if (share_token) { if (share_token) {
fetch(apiserver + "/api/shared-peer", { fetch(apiserver + "/api/shared-peer", {
@@ -37,14 +38,15 @@ if (share_token) {
body: JSON.stringify({share_token}) body: JSON.stringify({share_token})
}).then(res => res.json()).then(res => { }).then(res => res.json()).then(res => {
if (res.code === 0) { if (res.code === 0) {
localStorage.setItem('custom-rendezvous-server', res.data.id_server) localStorage.setItem(storage_prefix + 'custom-rendezvous-server', res.data.id_server)
localStorage.setItem('key', res.data.key) localStorage.setItem(storage_prefix + 'key', res.data.key)
const peer = res.data.peer const peer = res.data.peer || {}
localStorage.setItem('remote-id', peer.info.id) /*const s = {
peer.tmppwd = stringToUint8Array(window.atob(peer.tmppwd)).toString() id: peer.info.id,
const oldPeers = JSON.parse(localStorage.getItem('peers')) || {} password: peer.tmppwd,
oldPeers[peer.info.id] = peer }*/
localStorage.setItem('peers', JSON.stringify(oldPeers)) //修改location
window.location.href = `/webclient2/#/${peer.info.id}?password=${peer.tmppwd}`
} }
}) })
} }
@@ -55,8 +57,8 @@ export function getServerConf(token) {
if (!token) { if (!token) {
return return
} }
const prefix = 'wc-'
console.log('getServerConf', token) // console.log('getServerConf', token)
if (fetching) { if (fetching) {
return return
} }
@@ -71,9 +73,9 @@ export function getServerConf(token) {
).then(res => res.json()).then(res => { ).then(res => res.json()).then(res => {
fetching = false fetching = false
if (res.code === 0) { if (res.code === 0) {
if (!localStorage.getItem(prefix + 'custom-rendezvous-server') || !localStorage.getItem('key')) { if (!localStorage.getItem(storage_prefix + 'custom-rendezvous-server') || !localStorage.getItem('key')) {
localStorage.setItem(prefix + 'custom-rendezvous-server', res.data.id_server) localStorage.setItem(storage_prefix + 'custom-rendezvous-server', res.data.id_server)
localStorage.setItem(prefix + 'key', res.data.key) localStorage.setItem(storage_prefix + 'key', res.data.key)
} }
} }
}).catch(_ => { }).catch(_ => {

224287
resources/web2/main.dart.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -3,6 +3,7 @@ package service
import ( import (
"Gwen/global" "Gwen/global"
"Gwen/model" "Gwen/model"
"encoding/json"
"github.com/google/uuid" "github.com/google/uuid"
"gorm.io/gorm" "gorm.io/gorm"
"strings" "strings"
@@ -116,6 +117,16 @@ func (s *AddressBookService) List(page, pageSize uint, where func(tx *gorm.DB))
return return
} }
func (s *AddressBookService) FromPeer(peer *model.Peer) (a *model.AddressBook) {
a = &model.AddressBook{}
a.Id = peer.Id
a.Username = peer.Username
a.Hostname = peer.Hostname
a.UserId = peer.UserId
a.Platform = s.PlatformFromOs(peer.Os)
return a
}
// Create 创建 // Create 创建
func (s *AddressBookService) Create(u *model.AddressBook) error { func (s *AddressBookService) Create(u *model.AddressBook) error {
res := global.DB.Create(u).Error res := global.DB.Create(u).Error
@@ -318,3 +329,12 @@ func (s *AddressBookService) CheckCollectionOwner(uid uint, cid uint) bool {
p := s.CollectionInfoById(cid) p := s.CollectionInfoById(cid)
return p.UserId == uid return p.UserId == uid
} }
func (s *AddressBookService) BatchUpdateTags(abs []*model.AddressBook, tags []string) error {
ids := make([]uint, 0)
for _, ab := range abs {
ids = append(ids, ab.RowId)
}
tagsv, _ := json.Marshal(tags)
return global.DB.Model(&model.AddressBook{}).Where("row_id in ?", ids).Update("tags", tagsv).Error
}

View File

@@ -458,3 +458,7 @@ func (us *UserService) AutoRefreshAccessToken(ut *model.UserToken) {
us.RefreshAccessToken(ut) us.RefreshAccessToken(ut)
} }
} }
func (us *UserService) BatchDeleteUserToken(ids []uint) error {
return global.DB.Where("id in ?", ids).Delete(&model.UserToken{}).Error
}