Compare commits

..

17 Commits

Author SHA1 Message Date
lejianwen
2f1380f24a fix(webclient): Remove license warning (#235) 2025-05-13 13:11:19 +08:00
lejianwen
ece3328e94 feat(webclient): Web client to 1.4.0 2025-05-12 20:16:08 +08:00
lejianwen
fdd26d87be fix: PageSize (#225) 2025-05-06 19:08:18 +08:00
lejianwen
2ade0dda42 chore: Noelware/docker-manifest-action 2025-04-25 16:20:36 +08:00
lejianwen
a87ae5cf65 chore: Noelware/docker-manifest-action 2025-04-25 14:34:45 +08:00
lejianwen
fe7b8b53a6 style: Oauth page languages 2025-04-24 21:52:43 +08:00
lejianwen
b929f3efdb style: Remove useless configurations 2025-04-15 10:52:46 +08:00
lejianwen
f847fc076f fix: Low case (#149) 2025-04-15 10:46:21 +08:00
lejianwen
60d0a701ce fix: Share pwd 2025-04-15 10:09:56 +08:00
lejianwen
0dedaf6824 feat: Peer share to group 2025-04-14 19:12:40 +08:00
lejianwen
ab231b3fed feat: Add SysInfoVer endpoint and AppService for version retrieval 2025-04-07 16:38:21 +08:00
lejianwen
e7f28cca36 fix: Update peer based on the UUID (#180) 2025-04-02 09:50:16 +08:00
lejianwen
505e8aac4b feat: Add Korean translations validator (#168) 2025-04-02 09:42:29 +08:00
lejianwen
746e2a6052 fix: Get Uuids 2025-03-15 21:02:47 +08:00
lejianwen
dc03d5d83d style: Update peer last online time logic (#173) 2025-03-15 21:02:08 +08:00
lejianwen
b770ab178d feat(admin): Add filter by ip and username (#172) 2025-03-15 19:49:49 +08:00
Tao Chen
fd7e022e88 fix: rm varify password accidentally (#176) 2025-03-15 19:40:02 +08:00
43 changed files with 92195 additions and 86243 deletions

View File

@@ -380,7 +380,7 @@ jobs:
- name: Create and push manifest Docker Hub (:version) - name: Create and push manifest Docker Hub (:version)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@v0.2.3
with: with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }} base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64, extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
@@ -390,7 +390,7 @@ jobs:
- name: Create and push manifest GHCR (:version) - name: Create and push manifest GHCR (:version)
if: ${{ env.SKIP_GHCR == 'false' }} if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@v0.2.3
with: with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }} 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, extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
@@ -401,7 +401,7 @@ jobs:
- name: Create and push manifest Docker Hub (:latest) - name: Create and push manifest Docker Hub (:latest)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@v0.2.3
with: with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:latest base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:latest
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:latest-amd64, extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:latest-amd64,
@@ -411,7 +411,7 @@ jobs:
- name: Create and push manifest GHCR (:latest) - name: Create and push manifest GHCR (:latest)
if: ${{ env.SKIP_GHCR == 'false' }} if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@v0.2.3
with: with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:latest base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:latest
extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-amd64, extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-amd64,
@@ -422,7 +422,7 @@ jobs:
- name: Create and push Full S6 manifest Docker Hub (:version) - name: Create and push Full S6 manifest Docker Hub (:version)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@v0.2.3
with: with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:full-s6 base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:full-s6
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-amd64, extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-amd64,
@@ -433,7 +433,7 @@ jobs:
- name: Create and push Full S6 manifest GHCR (:latest) - name: Create and push Full S6 manifest GHCR (:latest)
if: ${{ env.SKIP_GHCR == 'false' }} if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@v0.2.3
with: with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:full-s6 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, extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-amd64,

View File

@@ -317,7 +317,7 @@ jobs:
- name: Create and push manifest Docker Hub (:version) - name: Create and push manifest Docker Hub (:version)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@v0.2.3
with: with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }} base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64, extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
@@ -327,7 +327,7 @@ jobs:
- name: Create and push manifest GHCR (:version) - name: Create and push manifest GHCR (:version)
if: ${{ env.SKIP_GHCR == 'false' }} if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@v0.2.3
with: with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }} 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, extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,

View File

@@ -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/

View File

@@ -64,21 +64,3 @@ ldap:
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.
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

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"
@@ -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)
} }

13
go.mod
View File

@@ -10,7 +10,7 @@ require (
github.com/gin-gonic/gin v1.9.0 github.com/gin-gonic/gin v1.9.0
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
@@ -22,7 +22,7 @@ require (
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/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/sqlite v1.5.6 gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.7 gorm.io/gorm v1.25.7
@@ -38,6 +38,7 @@ require (
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/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/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
@@ -56,7 +57,7 @@ require (
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
@@ -75,10 +76,10 @@ 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/crypto v0.33.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/sys v0.30.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // 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

View File

@@ -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 {
@@ -135,7 +135,7 @@ func (abcr *AddressBookCollectionRule) CheckForm(t *model.AddressBookCollectionR
return "ParamsError", false return "ParamsError", false
} }
// 重复检查 // 重复检查
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
} }

View File

@@ -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
} }
//非管理员不能分享给非本组织用户 //非管理员不能分享给非本组织用户
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 {
//非管理员不能分享给其他组 //非管理员不能分享给其他组
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 {
@@ -124,7 +124,7 @@ func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.Address
return "ParamsError", false return "ParamsError", false
} }
// 重复检查 // 重复检查
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
} }

View File

@@ -108,6 +108,12 @@ 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+"%")
}
}) })
response.Success(c, res) response.Success(c, res)
} }

View File

@@ -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

View File

@@ -7,7 +7,6 @@ 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"
) )
@@ -56,7 +55,7 @@ func (i *Index) Heartbeat(c *gin.Context) {
return return
} }
//如果在40s以内则不更新 //如果在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)
} }
@@ -74,13 +73,9 @@ func (i *Index) Heartbeat(c *gin.Context) {
// @Router /version [get] // @Router /version [get]
func (i *Index) Version(c *gin.Context) { func (i *Index) Version(c *gin.Context) {
//读取resources/version文件 //读取resources/version文件
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,
) )
} }

View File

@@ -8,6 +8,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"
"github.com/lejianwen/rustdesk-api/v2/utils"
"github.com/nicksnyder/go-i18n/v2/i18n"
"net/http" "net/http"
) )
@@ -145,7 +147,8 @@ 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
} }
@@ -155,7 +158,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
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
} }
@@ -169,7 +172,8 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
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
} }
@@ -182,7 +186,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
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
} }
@@ -190,7 +194,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
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
} }
@@ -198,12 +202,12 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
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
@@ -211,7 +215,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
//登录 //登录
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
} }
@@ -230,7 +234,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
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
} }
@@ -252,14 +256,50 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
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, "';")
}
}
//返回js内容
c.Header("Content-Type", "application/javascript")
c.String(http.StatusOK, res)
}

View File

@@ -30,7 +30,7 @@ func (p *Peer) SysInfo(c *gin.Context) {
return return
} }
fpe := f.ToPeer() fpe := f.ToPeer()
pe := service.AllService.PeerService.FindById(f.Id) pe := service.AllService.PeerService.FindByUuid(f.Uuid)
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)
@@ -56,3 +56,9 @@ func (p *Peer) SysInfo(c *gin.Context) {
//直接响应文本 //直接响应文本
c.String(http.StatusOK, "SYSINFO_UPDATED") c.String(http.StatusOK, "SYSINFO_UPDATED")
} }
func (p *Peer) SysInfoVer(c *gin.Context) {
//读取resources/version文件
v := service.AllService.AppService.GetAppVersion()
c.String(http.StatusOK, v)
}

View File

@@ -41,6 +41,8 @@ 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"`
} }
type SimpleDataQuery struct { type SimpleDataQuery struct {

View File

@@ -40,14 +40,14 @@ type LoginForm struct {
type UserListQuery struct { type UserListQuery struct {
Page uint `json:"page" form:"page" validate:"required" label:"页码"` Page uint `json:"page" form:"page" validate:"required" label:"页码"`
PageSize uint `json:"page_size" form:"page_size" validate:"required" label:"每页数量"` PageSize uint `json:"pageSize" form:"pageSize" validate:"required" label:"每页数量"`
Status int `json:"status" form:"status" label:"状态"` Status int `json:"status" form:"status" label:"状态"`
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:"页码"`
PageSize uint `json:"page_size" form:"page_size" validate:"required" label:"每页数量"` PageSize uint `json:"pageSize" form:"pageSize" validate:"required" label:"每页数量"`
Status int `json:"status" form:"status" label:"状态"` Status int `json:"status" form:"status" label:"状态"`
Accessible string `json:"accessible" form:"accessible"` Accessible string `json:"accessible" form:"accessible"`
} }

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
}

View File

@@ -48,11 +48,13 @@ 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)
} }
{ {
pe := &api.Peer{} pe := &api.Peer{}
//提交系统信息 //提交系统信息
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 {

View File

@@ -138,3 +138,8 @@ other = "Captcha error."
description = "Password login disabled." description = "Password login disabled."
one = "Password login disabled." one = "Password login disabled."
other = "Password login disabled." other = "Password login disabled."
[CannotShareToSelf]
description = "Cannot share to self."
one = "Cannot share to self."
other = "Cannot share to self."

View File

@@ -147,3 +147,8 @@ other = "Error de captcha."
description = "Password login disabled." description = "Password login disabled."
one = "Inicio de sesión con contraseña deshabilitado." one = "Inicio de sesión con contraseña deshabilitado."
other = "Inicio de sesión con contraseña deshabilitado." other = "Inicio de sesión con contraseña deshabilitado."
[CannotShareToSelf]
description = "Cannot share to self."
one = "No se puede compartir con uno mismo."
other = "No se puede compartir con uno mismo."

View File

@@ -147,3 +147,8 @@ other = "Erreur de captcha."
description = "Password login disabled." description = "Password login disabled."
one = "Connexion par mot de passe désactivée." one = "Connexion par mot de passe désactivée."
other = "Connexion par mot de passe désactivée." other = "Connexion par mot de passe désactivée."
[CannotShareToSelf]
description = "Cannot share to self."
one = "Impossible de partager avec soi-même."
other = "Impossible de partager avec soi-même."

View File

@@ -141,3 +141,8 @@ other = "Captcha 오류."
description = "Password login disabled." description = "Password login disabled."
one = "비밀번호 로그인이 비활성화되었습니다." one = "비밀번호 로그인이 비활성화되었습니다."
other = "비밀번호 로그인이 비활성화되었습니다." other = "비밀번호 로그인이 비활성화되었습니다."
[CannotShareToSelf]
description = "Cannot share to self."
one = "자기 자신에게 공유할 수 없습니다."
other = "자기 자신에게 공유할 수 없습니다."

View File

@@ -147,3 +147,8 @@ other = "Ошибка капчи."
description = "Password login disabled." description = "Password login disabled."
one = "Вход по паролю отключен." one = "Вход по паролю отключен."
other = "Вход по паролю отключен." other = "Вход по паролю отключен."
[CannotShareToSelf]
description = "Cannot share to self."
one = "Нельзя поделиться с собой."
other = "Нельзя поделиться с собой."

View File

@@ -140,3 +140,8 @@ other = "验证码错误。"
description = "Password login disabled." description = "Password login disabled."
one = "密码登录已禁用。" one = "密码登录已禁用。"
other = "密码登录已禁用。" other = "密码登录已禁用。"
[CannotShareToSelf]
description = "Cannot share to self."
one = "不能共享给自己。"
other = "不能共享给自己。"

View File

@@ -140,3 +140,8 @@ other = "驗證碼錯誤。"
description = "Password login disabled." description = "Password login disabled."
one = "密碼登錄已禁用。" one = "密碼登錄已禁用。"
other = "密碼登錄已禁用。" other = "密碼登錄已禁用。"
[CannotShareToSelf]
description = "Cannot share to self."
one = "無法共享給自己。"
other = "無法共享給自己。"

View File

View File

@@ -1,9 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-CN"> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>授权失败 - RustDesk API</title> <title>OauthFailed - RustDesk API</title>
<style> <style>
body { body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
@@ -57,17 +57,25 @@
} }
</style> </style>
<link rel="stylesheet" href="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/font-awesome/6.0.0/css/all.min.css"> <link rel="stylesheet" href="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/font-awesome/6.0.0/css/all.min.css">
<script>
var lang = navigator.language || navigator.userLanguage || 'zh-CN';
var title = 'OauthFailed'
var msg = '{{.message}}'
var btn = 'Close'
document.writeln('<script src="/api/oauth/msg?lang=' + lang + '&msg=' + msg + '&title=OauthFailed"><\/script>');
</script>
</head> </head>
<body> <body>
<div class="success-container"> <div class="success-container">
<i class="fas fa-triangle-exclamation checkmark"></i> <i class="fas fa-triangle-exclamation checkmark"></i>
<h1>授权失败!</h1> <h1 id="h1"></h1>
<p>{{.message}}</p> <p id="msg"></p>
<a href="javascript:window.close()" class="return-link">关闭页面</a> <a href="javascript:window.close()" class="return-link" id="btn">Close</a>
</div> </div>
<script> <script>
document.title = title + ' - RustDesk API';
document.getElementById('h1').innerText = title;
document.getElementById('msg').innerText = msg;
</script> </script>
</body> </body>
</html> </html>

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>授权成功 - RustDesk API</title> <title>OauthSuccess - RustDesk API</title>
<style> <style>
body { body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
@@ -56,18 +56,27 @@
background-color: #45a049; background-color: #45a049;
} }
</style> </style>
<script>
var lang = navigator.language || navigator.userLanguage || 'zh-CN';
var title = 'OauthSuccess'
var msg = '{{.message}}'
var btn = 'Close'
document.writeln('<script src="/api/oauth/msg?lang=' + lang + '&msg=' + msg + '&title=OauthSuccess"><\/script>');
</script>
</head> </head>
<body> <body>
<div class="success-container"> <div class="success-container">
<i class="fas fa-check-circle checkmark"></i> <i class="fas fa-check-circle checkmark"></i>
<h1>授权成功!</h1> <h1 id="h1"></h1>
<p>您已成功授权访问您的账户。</p> <!-- <p>您已成功授权访问您的账户。</p>-->
<p>现在可以关闭本页面或返回应用继续操作。</p> <!-- <p>现在可以关闭本页面或返回应用继续操作。</p>-->
<a href="javascript:window.close()" class="return-link">关闭页面</a> <a href="javascript:window.close()" class="return-link">Close</a>
</div> </div>
<script> <script>
document.title = title + ' - RustDesk API';
document.getElementById('h1').innerText = title;
document.getElementById('msg').innerText = msg;
</script> </script>
</body> </body>
</html> </html>

View File

@@ -38,5 +38,21 @@
"asset": "assets/address_book.ttf" "asset": "assets/address_book.ttf"
} }
] ]
},
{
"family": "DeviceGroup",
"fonts": [
{
"asset": "assets/device_group.ttf"
}
]
},
{
"family": "More",
"fonts": [
{
"asset": "assets/more.ttf"
}
]
} }
] ]

Binary file not shown.

BIN
resources/web2/assets/assets/more.ttf vendored Normal file

Binary file not shown.

View File

@@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<!-- <!--
If you are serving your web app in a path other than the root, change the If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from. href value below to reflect the base path you are serving from.
@@ -16,195 +16,196 @@
--> -->
<base href="/webclient2/" /> <base href="/webclient2/" />
<meta charset="UTF-8" /> <meta charset="UTF-8"/>
<meta content="IE=Edge" http-equiv="X-UA-Compatible" /> <meta content="IE=Edge" http-equiv="X-UA-Compatible"/>
<meta name="description" content="Remote Desktop." /> <meta name="description" content="Remote Desktop."/>
<!-- iOS meta tags & icons --> <!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black" /> <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<meta name="apple-mobile-web-app-title" content="RustDesk" /> <meta name="apple-mobile-web-app-title" content="RustDesk"/>
<link rel="apple-touch-icon" href="icons/Icon-192.png?v=1a7ad736" /> <link rel="apple-touch-icon" href="icons/Icon-192.png?v=1a7ad736"/>
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="favicon.svg?v=8fcccd9a" /> <link rel="icon" type="image/svg+xml" href="favicon.svg?v=8fcccd9a"/>
<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=cabfd933"></script> <script type="module" crossorigin src="js/dist/index.js?v=ddbe54f1"></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,
body, body,
#root { #root {
height: 100%; height: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
}
#root {
background-repeat: no-repeat;
background-size: 100% auto;
}
.loading-title {
font-size: 1.1rem;
}
.loading-sub-title {
margin-top: 20px;
font-size: 1rem;
color: #888;
}
.page-loading-warp {
display: flex;
align-items: center;
justify-content: center;
padding: 26px;
}
.ant-spin {
position: absolute;
display: none;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.65);
color: #1890ff;
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5;
text-align: center;
list-style: none;
opacity: 0;
-webkit-transition: -webkit-transform 0.3s
cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
-webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
-webkit-font-feature-settings: "tnum";
font-feature-settings: "tnum";
}
.ant-spin-spinning {
position: static;
display: inline-block;
opacity: 1;
}
.ant-spin-dot {
position: relative;
display: inline-block;
width: 20px;
height: 20px;
font-size: 20px;
}
.ant-spin-dot-item {
position: absolute;
display: block;
width: 9px;
height: 9px;
background-color: #1890ff;
border-radius: 100%;
-webkit-transform: scale(0.75);
-ms-transform: scale(0.75);
transform: scale(0.75);
-webkit-transform-origin: 50% 50%;
-ms-transform-origin: 50% 50%;
transform-origin: 50% 50%;
opacity: 0.3;
-webkit-animation: antspinmove 1s infinite linear alternate;
animation: antSpinMove 1s infinite linear alternate;
}
.ant-spin-dot-item:nth-child(1) {
top: 0;
left: 0;
}
.ant-spin-dot-item:nth-child(2) {
top: 0;
right: 0;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.ant-spin-dot-item:nth-child(3) {
right: 0;
bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.ant-spin-dot-item:nth-child(4) {
bottom: 0;
left: 0;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
.ant-spin-dot-spin {
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
-webkit-animation: antrotate 1.2s infinite linear;
animation: antRotate 1.2s infinite linear;
}
.ant-spin-lg .ant-spin-dot {
width: 32px;
height: 32px;
font-size: 32px;
}
.ant-spin-lg .ant-spin-dot i {
width: 14px;
height: 14px;
}
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
.ant-spin-blur {
background: #fff;
opacity: 0.5;
} }
}
@-webkit-keyframes antSpinMove { #root {
to { background-repeat: no-repeat;
opacity: 1; background-size: 100% auto;
} }
}
@keyframes antSpinMove { .loading-title {
to { font-size: 1.1rem;
opacity: 1;
} }
}
@-webkit-keyframes antRotate { .loading-sub-title {
to { margin-top: 20px;
-webkit-transform: rotate(405deg); font-size: 1rem;
transform: rotate(405deg); color: #888;
} }
}
@keyframes antRotate { .page-loading-warp {
to { display: flex;
-webkit-transform: rotate(405deg); align-items: center;
transform: rotate(405deg); justify-content: center;
padding: 26px;
}
.ant-spin {
position: absolute;
display: none;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.65);
color: #1890ff;
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5;
text-align: center;
list-style: none;
opacity: 0;
-webkit-transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
-webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
-webkit-font-feature-settings: "tnum";
font-feature-settings: "tnum";
}
.ant-spin-spinning {
position: static;
display: inline-block;
opacity: 1;
}
.ant-spin-dot {
position: relative;
display: inline-block;
width: 20px;
height: 20px;
font-size: 20px;
}
.ant-spin-dot-item {
position: absolute;
display: block;
width: 9px;
height: 9px;
background-color: #1890ff;
border-radius: 100%;
-webkit-transform: scale(0.75);
-ms-transform: scale(0.75);
transform: scale(0.75);
-webkit-transform-origin: 50% 50%;
-ms-transform-origin: 50% 50%;
transform-origin: 50% 50%;
opacity: 0.3;
-webkit-animation: antspinmove 1s infinite linear alternate;
animation: antSpinMove 1s infinite linear alternate;
}
.ant-spin-dot-item:nth-child(1) {
top: 0;
left: 0;
}
.ant-spin-dot-item:nth-child(2) {
top: 0;
right: 0;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.ant-spin-dot-item:nth-child(3) {
right: 0;
bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.ant-spin-dot-item:nth-child(4) {
bottom: 0;
left: 0;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
.ant-spin-dot-spin {
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
-webkit-animation: antrotate 1.2s infinite linear;
animation: antRotate 1.2s infinite linear;
}
.ant-spin-lg .ant-spin-dot {
width: 32px;
height: 32px;
font-size: 32px;
}
.ant-spin-lg .ant-spin-dot i {
width: 14px;
height: 14px;
}
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
.ant-spin-blur {
background: #fff;
opacity: 0.5;
}
}
@-webkit-keyframes antSpinMove {
to {
opacity: 1;
}
}
@keyframes antSpinMove {
to {
opacity: 1;
}
}
@-webkit-keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
} }
}
</style> </style>
</head> </head>
<body> <body>
<div id="root"> <div id="root">
<div <div
id="div-background" id="div-background"
style=" style="
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@@ -212,117 +213,119 @@
height: 100%; height: 100%;
min-height: 420px; min-height: 420px;
" "
> >
<img src="./favicon.svg?v=8fcccd9a" alt="logo" width="256" /> <img src="./favicon.svg?v=8fcccd9a" alt="logo" width="256"/>
<div class="page-loading-warp"> <div class="page-loading-warp">
<div class="ant-spin ant-spin-lg ant-spin-spinning"> <div class="ant-spin ant-spin-lg ant-spin-spinning">
<span class="ant-spin-dot ant-spin-dot-spin"> <span class="ant-spin-dot ant-spin-dot-spin">
<i class="ant-spin-dot-item"></i> <i class="ant-spin-dot-item"></i>
<i class="ant-spin-dot-item"></i> <i class="ant-spin-dot-item"></i>
<i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i> <i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i>
</span> </span>
</div> </div>
</div> </div>
<div <div
style="display: flex; align-items: center; justify-content: center" style="display: flex; align-items: center; justify-content: center"
> >
<img src="./favicon.svg?v=8fcccd9a" width="32" style="margin-right: 8px" /> <img src="./favicon.svg?v=8fcccd9a" width="32" style="margin-right: 8px"/>
<span id="span-text">RustDesk Web Client V2 Preview</span> <span id="span-text">RustDesk Web Client V2 Preview</span>
</div> </div>
</div>
</div> </div>
<!-- This script installs service_worker.js to provide PWA functionality to </div>
application. For more information, see: <!-- This script installs service_worker.js to provide PWA functionality to
https://developers.google.com/web/fundamentals/primers/service-workers --> application. For more information, see:
<script> https://developers.google.com/web/fundamentals/primers/service-workers -->
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") <script>
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches .matches
? "dark" ? "dark"
: "light"; : "light";
const myTheme = localStorage.getItem("wc-option:local:theme"); const myTheme = localStorage.getItem("wc-option:local:theme");
const them = myTheme || systemTheme; const them = myTheme || systemTheme;
const divBackground = document.querySelector("#div-background"); const divBackground = document.querySelector("#div-background");
if (divBackground) { if (divBackground) {
divBackground.style.backgroundColor = them === "dark" ? "#000" : "#fff"; divBackground.style.backgroundColor = them === "dark" ? "#000" : "#fff";
} }
const spanConsole = document.querySelector("#span-text"); const spanConsole = document.querySelector("#span-text");
if (spanConsole) { if (spanConsole) {
spanConsole.style.color = them === "dark" ? "#fff" : "#000"; spanConsole.style.color = them === "dark" ? "#fff" : "#000";
} }
const serviceWorkerVersion = "3267265270"; const serviceWorkerVersion = "461457302";
var scriptLoaded = false; var scriptLoaded = false;
function loadMainDartJs() {
function loadMainDartJs() {
if (scriptLoaded) { if (scriptLoaded) {
return; return;
} }
scriptLoaded = true; scriptLoaded = true;
var scriptTag = document.createElement("script"); var scriptTag = document.createElement("script");
scriptTag.src = "main.dart.js?v=060a626e"; scriptTag.src = "main.dart.js?v=6d16cb80";
scriptTag.type = "application/javascript"; scriptTag.type = "application/javascript";
document.body.append(scriptTag); document.body.append(scriptTag);
} }
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
// Service workers are supported. Use them. // Service workers are supported. Use them.
window.addEventListener("load", function () { window.addEventListener("load", function () {
// Wait for registration to finish before dropping the <script> tag. // Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times, // Otherwise, the browser will load the script multiple times,
// potentially different versions. // potentially different versions.
var serviceWorkerUrl = var serviceWorkerUrl =
"flutter_service_worker.js?v=" + serviceWorkerVersion; "flutter_service_worker.js?v=" + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl).then((reg) => { navigator.serviceWorker.register(serviceWorkerUrl).then((reg) => {
function waitForActivation(serviceWorker) { function waitForActivation(serviceWorker) {
serviceWorker.addEventListener("statechange", () => { serviceWorker.addEventListener("statechange", () => {
if (serviceWorker.state == "activated") { if (serviceWorker.state == "activated") {
console.log("Installed new service worker."); console.log("Installed new service worker.");
loadMainDartJs(); loadMainDartJs();
}
});
} }
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing || reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log("New service worker available.");
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log("Loading app from service worker.");
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time, if (!reg.active && (reg.installing || reg.waiting)) {
// fallback to plaint <script> tag. // No active web worker and we have installed or are installing
setTimeout(() => { // one for the first time. Simply wait for it to activate.
if (!scriptLoaded) { waitForActivation(reg.installing || reg.waiting);
console.warn( } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
"Failed to load app from service worker. Falling back to plain <script> tag." // When the app updates the serviceWorkerVersion changes, so we
); // need to ask the service worker to update.
loadMainDartJs(); console.log("New service worker available.");
} reg.update();
}, 4000); waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log("Loading app from service worker.");
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
"Failed to load app from service worker. Falling back to plain <script> tag."
);
loadMainDartJs();
}
}, 4000);
}); });
} else { } else {
// Service workers not supported. Just drop the <script> tag. // Service workers not supported. Just drop the <script> tag.
loadMainDartJs(); loadMainDartJs();
} }
</script> </script>
<script src="libs/stream/ponyfill.min.js"></script> <script src="libs/stream/ponyfill.min.js"></script>
<script src="libs/stream/StreamSaver.min.js"></script> <script src="libs/stream/StreamSaver.min.js"></script>
<script src="libs/firebase-app.js?8.10.1"></script> <script src="libs/firebase-app.js?8.10.1"></script>
<script src="libs/firebase-analytics.js?8.10.1"></script> <script src="libs/firebase-analytics.js?8.10.1"></script>
<script> <script>
// Your web app's Firebase configuration // Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional // For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = { const firebaseConfig = {
apiKey: "AIzaSyCgehIZk1aFP0E7wZtYRRqrfvNiNAF39-A", apiKey: "AIzaSyCgehIZk1aFP0E7wZtYRRqrfvNiNAF39-A",
authDomain: "rustdesk.firebaseapp.com", authDomain: "rustdesk.firebaseapp.com",
databaseURL: "https://rustdesk.firebaseio.com", databaseURL: "https://rustdesk.firebaseio.com",
@@ -331,11 +334,11 @@
messagingSenderId: "768133699366", messagingSenderId: "768133699366",
appId: "1:768133699366:web:d50faf0792cb208d7993e7", appId: "1:768133699366:web:d50faf0792cb208d7993e7",
measurementId: "G-9PEH85N6ZQ", measurementId: "G-9PEH85N6ZQ",
}; };
// Initialize Firebase // Initialize Firebase
firebase.initializeApp(firebaseConfig); firebase.initializeApp(firebaseConfig);
firebase.analytics(); firebase.analytics();
</script> </script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,11 @@
window._gwen = {} window._gwen = {}
window._gwen.kv = {} window._gwen.kv = {}
//fix 语言
if(!localStorage.getItem('wc-option:local:lang') && navigator.language){
localStorage.setItem('wc-option:local:lang', navigator.language.toLowerCase())
}
const storage_prefix = 'wc-' const storage_prefix = 'wc-'
const apiserver = localStorage.getItem('wc-api-server') const apiserver = localStorage.getItem('wc-api-server')
@@ -46,7 +52,7 @@ if (share_token) {
password: peer.tmppwd, password: peer.tmppwd,
}*/ }*/
//修改location //修改location
window.location.href = `/webclient2/#/${peer.info.id}?password=${peer.tmppwd}` window.location.href = `/webclient2/#/${peer.info.id}?password=${encodeURIComponent(peer.tmppwd)}`
} }
}) })
} }

163526
resources/web2/main.dart.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -293,8 +293,11 @@ func (s *AddressBookService) RuleInfoById(u uint) *model.AddressBookCollectionRu
return p return p
} }
func (s *AddressBookService) RulePersonalInfoByToIdAndCid(toid, cid uint) *model.AddressBookCollectionRule { func (s *AddressBookService) RulePersonalInfoByToIdAndCid(toid, cid uint) *model.AddressBookCollectionRule {
return s.RuleInfoByToIdAndCid(model.ShareAddressBookRuleTypePersonal, toid, cid)
}
func (s *AddressBookService) RuleInfoByToIdAndCid(t int, toid, cid uint) *model.AddressBookCollectionRule {
p := &model.AddressBookCollectionRule{} p := &model.AddressBookCollectionRule{}
DB.Where("type = ? and to_id = ? and collection_id = ?", model.ShareAddressBookRuleTypePersonal, toid, cid).First(p) DB.Where("type = ? and to_id = ? and collection_id = ?", t, toid, cid).First(p)
return p return p
} }
func (s *AddressBookService) CreateRule(t *model.AddressBookCollectionRule) error { func (s *AddressBookService) CreateRule(t *model.AddressBookCollectionRule) error {

28
service/app.go Normal file
View File

@@ -0,0 +1,28 @@
package service
import (
"os"
"sync"
)
type AppService struct {
}
var version = ""
var once = &sync.Once{}
func (a *AppService) GetAppVersion() string {
if version != "" {
return version
}
once.Do(func() {
v, err := os.ReadFile("resources/version")
if err != nil {
return
}
version = string(v)
})
return version
}

33
service/app_test.go Normal file
View File

@@ -0,0 +1,33 @@
package service
import (
"sync"
"testing"
)
// TestGetAppVersion
func TestGetAppVersion(t *testing.T) {
s := &AppService{}
v := s.GetAppVersion()
// 打印结果
t.Logf("App Version: %s", v)
}
func TestMultipleGetAppVersion(t *testing.T) {
s := &AppService{}
//并发测试
// 使用 WaitGroup 等待所有 goroutine 完成
wg := sync.WaitGroup{}
wg.Add(10) // 启动 10 个 goroutine
// 启动 10 个 goroutine
for i := 0; i < 10; i++ {
go func() {
defer wg.Done() // 完成后减少计数
v := s.GetAppVersion()
// 打印结果
t.Logf("App Version: %s", v)
}()
}
// 等待所有 goroutine 完成
wg.Wait()
}

View File

@@ -30,6 +30,7 @@ var (
ErrLdapBindFailed = errors.New("LdapBindFailed") ErrLdapBindFailed = errors.New("LdapBindFailed")
ErrLdapToLocalUserFailed = errors.New("LdapToLocalUserFailed") ErrLdapToLocalUserFailed = errors.New("LdapToLocalUserFailed")
ErrLdapCreateUserFailed = errors.New("LdapCreateUserFailed") ErrLdapCreateUserFailed = errors.New("LdapCreateUserFailed")
ErrLdapPasswordNotMatch = errors.New("PasswordNotMatch")
) )
// LdapService is responsible for LDAP authentication and user synchronization. // LdapService is responsible for LDAP authentication and user synchronization.
@@ -119,7 +120,7 @@ func (ls *LdapService) connectAndBindAdmin(cfg *config.Ldap) (*ldap.Conn, error)
func (ls *LdapService) verifyCredentials(cfg *config.Ldap, username, password string) error { func (ls *LdapService) verifyCredentials(cfg *config.Ldap, username, password string) error {
ldapConn, err := ls.connectAndBind(cfg, username, password) ldapConn, err := ls.connectAndBind(cfg, username, password)
if err != nil { if err != nil {
return err return ErrLdapPasswordNotMatch
} }
defer ldapConn.Close() defer ldapConn.Close()
return nil return nil
@@ -136,6 +137,10 @@ func (ls *LdapService) Authenticate(username, password string) (*model.User, err
return nil, ErrLdapUserDisabled return nil, ErrLdapUserDisabled
} }
cfg := &Config.Ldap cfg := &Config.Ldap
err = ls.verifyCredentials(cfg, ldapUser.Dn, password)
if err != nil {
return nil, err
}
user, err := ls.mapToLocalUser(cfg, ldapUser) user, err := ls.mapToLocalUser(cfg, ldapUser)
if err != nil { if err != nil {
return nil, errors.Join(ErrLdapToLocalUserFailed, err) return nil, errors.Join(ErrLdapToLocalUserFailed, err)

View File

@@ -126,7 +126,14 @@ func (ps *PeerService) GetUuidListByIDs(ids []uint) ([]string, error) {
err := DB.Model(&model.Peer{}). err := DB.Model(&model.Peer{}).
Where("row_id in (?)", ids). Where("row_id in (?)", ids).
Pluck("uuid", &uuids).Error Pluck("uuid", &uuids).Error
return uuids, err //过滤uuids中的空字符串
var newUuids []string
for _, uuid := range uuids {
if uuid != "" {
newUuids = append(newUuids, uuid)
}
}
return newUuids, err
} }
// BatchDelete 批量删除, 同时也应该删除token // BatchDelete 批量删除, 同时也应该删除token

View File

@@ -23,6 +23,7 @@ type Service struct {
*ShareRecordService *ShareRecordService
*ServerCmdService *ServerCmdService
*LdapService *LdapService
*AppService
} }
type Dependencies struct { type Dependencies struct {

View File

@@ -7,6 +7,7 @@ import (
"math/rand" "math/rand"
"reflect" "reflect"
"runtime/debug" "runtime/debug"
"strings"
) )
func Md5(str string) string { func Md5(str string) string {
@@ -100,3 +101,11 @@ func InArray(k string, arr []string) bool {
} }
return false return false
} }
func StringConcat(strs ...string) string {
var builder strings.Builder
for _, str := range strs {
builder.WriteString(str)
}
return builder.String()
}