├── .env.development ├── .env.production ├── .gitignore ├── LICENSE ├── README.md ├── index.html ├── jsconfig.json ├── package-lock.json ├── package.json ├── public └── favicon.ico ├── src ├── App.vue ├── api │ ├── address_book.js │ ├── address_book_collection.js │ ├── address_book_collection_rule.js │ ├── audit.js │ ├── config.js │ ├── device_group.js │ ├── file.js │ ├── group.js │ ├── login.js │ ├── login_log.js │ ├── my │ │ ├── address_book.js │ │ ├── address_book_collection.js │ │ ├── address_book_collection_rule.js │ │ ├── login_log.js │ │ ├── peer.js │ │ ├── share_record.js │ │ └── tag.js │ ├── oauth.js │ ├── peer.js │ ├── rustdesk.js │ ├── share_record.js │ ├── tag.js │ ├── user.js │ └── user_token.js ├── assets │ ├── github.png │ ├── google.png │ ├── logo.png │ ├── oidc.png │ └── webauth.png ├── components │ ├── changePwdDialog.vue │ ├── form │ │ ├── address.vue │ │ └── upload │ │ │ ├── imageUpload.vue │ │ │ ├── imagesUpload.vue │ │ │ ├── local.js │ │ │ └── oss.js │ └── icons │ │ └── platform.vue ├── global.js ├── layout │ ├── components │ │ ├── aside.vue │ │ ├── header.vue │ │ ├── menu │ │ │ ├── index.vue │ │ │ └── item.vue │ │ ├── setting │ │ │ └── index.vue │ │ └── tags │ │ │ └── index.vue │ └── index.vue ├── main.js ├── permission.js ├── router │ └── index.js ├── store │ ├── app.js │ ├── index.js │ ├── router.js │ ├── tags.js │ └── user.js ├── styles │ └── style.scss ├── utils │ ├── auth.js │ ├── clipboard.js │ ├── common_options.js │ ├── file.js │ ├── i18n.js │ ├── i18n │ │ ├── en.json │ │ ├── es.json │ │ ├── fr.json │ │ ├── ko.json │ │ ├── ru.json │ │ ├── zh_CN.json │ │ └── zh_TW.json │ ├── pca.json │ ├── peer.js │ ├── request.js │ ├── time.js │ ├── webclient.js │ └── webclient │ │ ├── message.ts │ │ ├── rendezvous.ts │ │ └── websock.ts └── views │ ├── address_book │ ├── collection.js │ ├── collection.vue │ ├── components │ │ └── shareByWebClient.vue │ ├── index.js │ ├── index.vue │ ├── rule.js │ └── rule.vue │ ├── audit │ ├── connList.vue │ ├── fileList.vue │ └── reponsitories.js │ ├── error-page │ └── 404.vue │ ├── group │ ├── deviceGroupList.vue │ └── index.vue │ ├── index │ └── index.vue │ ├── login │ ├── log.js │ ├── log.vue │ └── login.vue │ ├── my │ ├── address_book │ │ ├── collection.vue │ │ ├── index.vue │ │ └── indexv2.vue │ ├── info.vue │ ├── login_log │ │ └── index.vue │ ├── peer │ │ └── index.vue │ ├── share_record │ │ └── index.vue │ └── tag │ │ └── index.vue │ ├── oauth │ ├── bind.vue │ ├── index.vue │ └── login.vue │ ├── peer │ ├── createABForm.vue │ └── index.vue │ ├── register │ └── index.vue │ ├── rustdesk │ ├── always_use_relay.vue │ ├── blacklist.vue │ ├── blocklist.vue │ ├── control.vue │ ├── must_login.vue │ ├── options.js │ ├── relay_servers.vue │ └── usage.vue │ ├── share_record │ ├── index.js │ └── index.vue │ ├── tag │ ├── index.js │ └── index.vue │ └── user │ ├── composables │ ├── edit.js │ └── index.js │ ├── edit.vue │ ├── index.vue │ ├── token.js │ └── token.vue └── vite.config.js /.env.development: -------------------------------------------------------------------------------- 1 | ENV = 'development' 2 | 3 | VITE_DEV_PORT = 8888 4 | VITE_SERVER_API = /api/admin 5 | VITE_SERVER_PATH = http://127.0.0.1:21114 6 | 7 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | ENV = 'production' 2 | 3 | VITE_DEV_PORT = 5000 4 | VITE_SERVER_API =/api/admin 5 | VITE_SERVER_PATH = http://127.0.0.1:5000 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | **/*.log 8 | 9 | 10 | # Editor directories and files 11 | .idea 12 | .vscode 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | *.local 18 | 19 | yarn.lock 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2021 vue-manage-system 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RustDesk API Web 2 | # 基于 Vue3 + Element Plus 的后台, 适用于 [RustDesk API](https://github.com/lejianwen/rustdesk-api) 3 | 4 | 5 | vue3 6 | 7 | 8 | element-plus 9 | 10 | 11 | license 12 | 13 | 14 | # 安装步骤 15 | 16 | ```shell 17 | git clone https://github.com/lejianwen/rustdesk-api-web 18 | cd rustdesk-api-web 19 | npm install 20 | 21 | // 本地开发 22 | npm run dev 23 | 24 | // 打包 25 | npm run build 26 | 27 | ``` 28 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Rustdesk API Admin 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": [ 6 | "src/*" 7 | ] 8 | } 9 | }, 10 | "exclude": [ 11 | "node_modules" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-vue3", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite --host", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "dependencies": { 10 | "axios": "1.7.4", 11 | "clipboard": "2.0.4", 12 | "element-plus": "^2.8.2", 13 | "fast-sha256": "^1.3.0", 14 | "js-cookie": "^3.0.1", 15 | "marked": "^15.0.4", 16 | "normalize.css": "^8.0.1", 17 | "nprogress": "^0.2.0", 18 | "pinia": "2.2.8", 19 | "vue": "3.5.13", 20 | "vue-router": "^4.0.12" 21 | }, 22 | "devDependencies": { 23 | "@element-plus/icons": "0.0.11", 24 | "@vitejs/plugin-vue": "5.2.1", 25 | "dotenv": "^10.0.0", 26 | "qs": "^6.10.2", 27 | "sass": "^1.43.4", 28 | "sass-loader": "^12.3.0", 29 | "ts-proto": "^1.141.1", 30 | "vite": "6.0.9" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api-web/3084e40039b9421422766299296f8483cbf89561/public/favicon.ico -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 18 | 20 | -------------------------------------------------------------------------------- /src/api/address_book.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/address_book/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function detail (id) { 11 | return request({ 12 | url: `/address_book/detail/${id}`, 13 | }) 14 | } 15 | 16 | export function create (data) { 17 | return request({ 18 | url: '/address_book/create', 19 | method: 'post', 20 | data, 21 | }) 22 | } 23 | 24 | export function update (data) { 25 | return request({ 26 | url: '/address_book/update', 27 | method: 'post', 28 | data, 29 | }) 30 | } 31 | 32 | export function remove (data) { 33 | return request({ 34 | url: '/address_book/delete', 35 | method: 'post', 36 | data, 37 | }) 38 | } 39 | 40 | export function batchCreate (data) { 41 | return request({ 42 | url: '/address_book/batchCreate', 43 | method: 'post', 44 | data, 45 | }) 46 | } 47 | 48 | export function shareByWebClient (data) { 49 | return request({ 50 | url: '/address_book/shareByWebClient', 51 | method: 'post', 52 | data, 53 | }) 54 | } 55 | 56 | export function batchCreateFromPeers (data) { 57 | return request({ 58 | url: '/address_book/batchCreateFromPeers', 59 | method: 'post', 60 | data, 61 | }) 62 | } 63 | 64 | -------------------------------------------------------------------------------- /src/api/address_book_collection.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/address_book_collection/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function detail (id) { 11 | return request({ 12 | url: `/address_book_collection/detail/${id}`, 13 | }) 14 | } 15 | 16 | export function create (data) { 17 | return request({ 18 | url: '/address_book_collection/create', 19 | method: 'post', 20 | data, 21 | }) 22 | } 23 | 24 | export function update (data) { 25 | return request({ 26 | url: '/address_book_collection/update', 27 | method: 'post', 28 | data, 29 | }) 30 | } 31 | 32 | export function remove (data) { 33 | return request({ 34 | url: '/address_book_collection/delete', 35 | method: 'post', 36 | data, 37 | }) 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/api/address_book_collection_rule.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/address_book_collection_rule/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function detail (id) { 11 | return request({ 12 | url: `/address_book_collection_rule/detail/${id}`, 13 | }) 14 | } 15 | 16 | export function create (data) { 17 | return request({ 18 | url: '/address_book_collection_rule/create', 19 | method: 'post', 20 | data, 21 | }) 22 | } 23 | 24 | export function update (data) { 25 | return request({ 26 | url: '/address_book_collection_rule/update', 27 | method: 'post', 28 | data, 29 | }) 30 | } 31 | 32 | export function remove (data) { 33 | return request({ 34 | url: '/address_book_collection_rule/delete', 35 | method: 'post', 36 | data, 37 | }) 38 | } 39 | 40 | export function batchCreate (data) { 41 | return request({ 42 | url: '/address_book_collection_rule/batchCreate', 43 | method: 'post', 44 | data, 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /src/api/audit.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/audit_conn/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function remove (data) { 11 | return request({ 12 | url: '/audit_conn/delete', 13 | method: 'post', 14 | data, 15 | }) 16 | } 17 | 18 | export function batchDelete (data) { 19 | return request({ 20 | url: '/audit_conn/batchDelete', 21 | method: 'post', 22 | data, 23 | }) 24 | } 25 | 26 | export function fileList (params) { 27 | return request({ 28 | url: '/audit_file/list', 29 | params, 30 | }) 31 | } 32 | 33 | export function fileRemove (data) { 34 | return request({ 35 | url: '/audit_file/delete', 36 | method: 'post', 37 | data, 38 | }) 39 | } 40 | 41 | export function fileBatchDelete (data) { 42 | return request({ 43 | url: '/audit_file/batchDelete', 44 | method: 'post', 45 | data, 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /src/api/config.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function server () { 4 | return request({ 5 | url: '/config/server', 6 | method: 'get', 7 | }) 8 | } 9 | 10 | export function app () { 11 | return request({ 12 | url: '/config/app', 13 | method: 'get', 14 | }) 15 | } 16 | 17 | export function admin () { 18 | return request({ 19 | url: '/config/admin', 20 | method: 'get', 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /src/api/device_group.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/device_group/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function detail (id) { 11 | return request({ 12 | url: `/device_group/detail/${id}`, 13 | }) 14 | } 15 | 16 | export function create (data) { 17 | return request({ 18 | url: '/device_group/create', 19 | method: 'post', 20 | data, 21 | }) 22 | } 23 | 24 | export function update (data) { 25 | return request({ 26 | url: '/device_group/update', 27 | method: 'post', 28 | data, 29 | }) 30 | } 31 | 32 | export function remove (data) { 33 | return request({ 34 | url: '/device_group/delete', 35 | method: 'post', 36 | data, 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /src/api/file.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function ossToken () { 4 | return request({ 5 | url: '/file/oss_token', 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /src/api/group.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/group/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function detail (id) { 11 | return request({ 12 | url: `/group/detail/${id}`, 13 | }) 14 | } 15 | 16 | export function create (data) { 17 | return request({ 18 | url: '/group/create', 19 | method: 'post', 20 | data, 21 | }) 22 | } 23 | 24 | export function update (data) { 25 | return request({ 26 | url: '/group/update', 27 | method: 'post', 28 | data, 29 | }) 30 | } 31 | 32 | export function remove (data) { 33 | return request({ 34 | url: '/group/delete', 35 | method: 'post', 36 | data, 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function loginOptions () { 4 | return request({ 5 | url: '/login-options', 6 | method: 'get', 7 | }) 8 | } 9 | 10 | export function oidcAuth (data) { 11 | return request({ 12 | url: '/oidc/auth', 13 | method: 'post', 14 | data, 15 | }) 16 | } 17 | 18 | export function oidcQuery (params) { 19 | return request({ 20 | url: '/oidc/auth-query', 21 | method: 'get', 22 | params, 23 | }) 24 | } 25 | 26 | export function captcha () { 27 | return request({ 28 | url: '/captcha', 29 | method: 'get', 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /src/api/login_log.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/login_log/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function remove (data) { 11 | return request({ 12 | url: '/login_log/delete', 13 | method: 'post', 14 | data, 15 | }) 16 | } 17 | 18 | export function batchDelete (data) { 19 | return request({ 20 | url: '/login_log/batchDelete', 21 | method: 'post', 22 | data, 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /src/api/my/address_book.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/my/address_book/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function batchCreateFromPeers (data) { 11 | return request({ 12 | url: '/my/address_book/batchCreateFromPeers', 13 | method: 'post', 14 | data, 15 | }) 16 | } 17 | 18 | export function batchUpdateTags (data) { 19 | return request({ 20 | url: '/my/address_book/batchUpdateTags', 21 | method: 'post', 22 | data, 23 | }) 24 | } 25 | 26 | export function create (data) { 27 | return request({ 28 | url: '/my/address_book/create', 29 | method: 'post', 30 | data, 31 | }) 32 | } 33 | 34 | export function update (data) { 35 | return request({ 36 | url: '/my/address_book/update', 37 | method: 'post', 38 | data, 39 | }) 40 | } 41 | 42 | export function remove (data) { 43 | return request({ 44 | url: '/my/address_book/delete', 45 | method: 'post', 46 | data, 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /src/api/my/address_book_collection.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/my/address_book_collection/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function create (data) { 11 | return request({ 12 | url: '/my/address_book_collection/create', 13 | method: 'post', 14 | data, 15 | }) 16 | } 17 | 18 | export function update (data) { 19 | return request({ 20 | url: '/my/address_book_collection/update', 21 | method: 'post', 22 | data, 23 | }) 24 | } 25 | 26 | export function remove (data) { 27 | return request({ 28 | url: '/my/address_book_collection/delete', 29 | method: 'post', 30 | data, 31 | }) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/api/my/address_book_collection_rule.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/my/address_book_collection_rule/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function create (data) { 11 | return request({ 12 | url: '/my/address_book_collection_rule/create', 13 | method: 'post', 14 | data, 15 | }) 16 | } 17 | 18 | export function update (data) { 19 | return request({ 20 | url: '/my/address_book_collection_rule/update', 21 | method: 'post', 22 | data, 23 | }) 24 | } 25 | 26 | export function remove (data) { 27 | return request({ 28 | url: '/my/address_book_collection_rule/delete', 29 | method: 'post', 30 | data, 31 | }) 32 | } 33 | 34 | export function batchCreate (data) { 35 | return request({ 36 | url: '/my/address_book_collection_rule/batchCreate', 37 | method: 'post', 38 | data, 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /src/api/my/login_log.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/my/login_log/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function remove (data) { 11 | return request({ 12 | url: '/my/login_log/delete', 13 | method: 'post', 14 | data, 15 | }) 16 | } 17 | 18 | export function batchDelete (data) { 19 | return request({ 20 | url: '/my/login_log/batchDelete', 21 | method: 'post', 22 | data, 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /src/api/my/peer.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/my/peer/list', 6 | params, 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /src/api/my/share_record.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/my/share_record/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function remove (data) { 11 | return request({ 12 | url: '/my/share_record/delete', 13 | method: 'post', 14 | data, 15 | }) 16 | } 17 | 18 | export function batchDelete (data) { 19 | return request({ 20 | url: '/my/share_record/batchDelete', 21 | method: 'post', 22 | data, 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /src/api/my/tag.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/my/tag/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function create (data) { 11 | return request({ 12 | url: '/my/tag/create', 13 | method: 'post', 14 | data, 15 | }) 16 | } 17 | 18 | export function update (data) { 19 | return request({ 20 | url: '/my/tag/update', 21 | method: 'post', 22 | data, 23 | }) 24 | } 25 | 26 | export function remove (data) { 27 | return request({ 28 | url: '/my/tag/delete', 29 | method: 'post', 30 | data, 31 | }) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/api/oauth.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function info (params) { 4 | return request({ 5 | url: '/oauth/info', 6 | params, 7 | }) 8 | 9 | } 10 | 11 | export function confirm (data) { 12 | return request({ 13 | url: '/oauth/confirm', 14 | method: 'post', 15 | data, 16 | }) 17 | } 18 | 19 | export function bind (data) { 20 | return request({ 21 | url: '/oauth/bind', 22 | method: 'post', 23 | data, 24 | }) 25 | } 26 | 27 | export function bindConfirm (data) { 28 | return request({ 29 | url: '/oauth/bindConfirm', 30 | method: 'post', 31 | data, 32 | }) 33 | } 34 | export function unbind (data) { 35 | return request({ 36 | url: '/oauth/unbind', 37 | method: 'post', 38 | data, 39 | }) 40 | } 41 | export function list (params) { 42 | return request({ 43 | url: '/oauth/list', 44 | params, 45 | }) 46 | } 47 | 48 | export function detail (id) { 49 | return request({ 50 | url: `/oauth/detail/${id}`, 51 | }) 52 | } 53 | 54 | export function create (data) { 55 | return request({ 56 | url: '/oauth/create', 57 | method: 'post', 58 | data, 59 | }) 60 | } 61 | 62 | export function update (data) { 63 | return request({ 64 | url: '/oauth/update', 65 | method: 'post', 66 | data, 67 | }) 68 | } 69 | 70 | export function remove (data) { 71 | return request({ 72 | url: '/oauth/delete', 73 | method: 'post', 74 | data, 75 | }) 76 | } 77 | -------------------------------------------------------------------------------- /src/api/peer.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/peer/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function detail (id) { 11 | return request({ 12 | url: `/peer/detail/${id}`, 13 | }) 14 | } 15 | 16 | export function create (data) { 17 | return request({ 18 | url: '/peer/create', 19 | method: 'post', 20 | data, 21 | }) 22 | } 23 | 24 | export function update (data) { 25 | return request({ 26 | url: '/peer/update', 27 | method: 'post', 28 | data, 29 | }) 30 | } 31 | 32 | export function remove (data) { 33 | return request({ 34 | url: '/peer/delete', 35 | method: 'post', 36 | data, 37 | }) 38 | } 39 | 40 | export function batchRemove (data) { 41 | return request({ 42 | url: '/peer/batchDelete', 43 | method: 'post', 44 | data, 45 | }) 46 | } 47 | 48 | export function simpleData (data) { 49 | return request({ 50 | url: '/peer/simpleData', 51 | method: 'post', 52 | data, 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /src/api/rustdesk.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/rustdesk/cmdList', 6 | params, 7 | }) 8 | } 9 | 10 | export function create (data) { 11 | return request({ 12 | url: '/rustdesk/cmdCreate', 13 | method: 'post', 14 | data, 15 | }) 16 | } 17 | 18 | export function update (data) { 19 | return request({ 20 | url: '/rustdesk/cmdUpdate', 21 | method: 'post', 22 | data, 23 | }) 24 | } 25 | 26 | export function remove (data) { 27 | return request({ 28 | url: '/rustdesk/cmdDelete', 29 | method: 'post', 30 | data, 31 | }) 32 | } 33 | 34 | export function sendCmd (data) { 35 | return request({ 36 | url: '/rustdesk/sendCmd', 37 | method: 'post', 38 | data, 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /src/api/share_record.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/share_record/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function remove (data) { 11 | return request({ 12 | url: '/share_record/delete', 13 | method: 'post', 14 | data, 15 | }) 16 | } 17 | 18 | export function batchDelete (data) { 19 | return request({ 20 | url: '/share_record/batchDelete', 21 | method: 'post', 22 | data, 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /src/api/tag.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/tag/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function detail (id) { 11 | return request({ 12 | url: `/tag/detail/${id}`, 13 | }) 14 | } 15 | 16 | export function create (data) { 17 | return request({ 18 | url: '/tag/create', 19 | method: 'post', 20 | data, 21 | }) 22 | } 23 | 24 | export function update (data) { 25 | return request({ 26 | url: '/tag/update', 27 | method: 'post', 28 | data, 29 | }) 30 | } 31 | 32 | export function remove (data) { 33 | return request({ 34 | url: '/tag/delete', 35 | method: 'post', 36 | data, 37 | }) 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/api/user.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function login (data) { 4 | return request({ 5 | url: '/login', 6 | method: 'post', 7 | data, 8 | }) 9 | } 10 | 11 | export function current () { 12 | return request({ 13 | url: '/user/current', 14 | method: 'get', 15 | }) 16 | } 17 | 18 | export function list (params) { 19 | return request({ 20 | url: '/user/list', 21 | params, 22 | }) 23 | } 24 | 25 | export function detail (id) { 26 | return request({ 27 | url: `/user/detail/${id}`, 28 | }) 29 | } 30 | 31 | export function create (data) { 32 | return request({ 33 | url: '/user/create', 34 | method: 'post', 35 | data, 36 | }) 37 | } 38 | 39 | export function update (data) { 40 | return request({ 41 | url: '/user/update', 42 | method: 'post', 43 | data, 44 | }) 45 | } 46 | 47 | export function remove (data) { 48 | return request({ 49 | url: '/user/delete', 50 | method: 'post', 51 | data, 52 | }) 53 | } 54 | 55 | export function changePwd (data) { 56 | return request({ 57 | url: '/user/changePwd', 58 | method: 'post', 59 | data, 60 | }) 61 | } 62 | 63 | export function changeCurPwd (data) { 64 | return request({ 65 | url: '/user/changeCurPwd', 66 | method: 'post', 67 | data, 68 | }) 69 | } 70 | 71 | export function myOauth () { 72 | return request({ 73 | url: '/user/myOauth', 74 | method: 'post', 75 | }) 76 | } 77 | 78 | export function myPeer (params) { 79 | return request({ 80 | url: '/user/myPeer', 81 | params, 82 | }) 83 | } 84 | 85 | export function groupUsers (data) { 86 | return request({ 87 | url: '/user/groupUsers', 88 | method: 'post', 89 | data, 90 | }) 91 | } 92 | 93 | export function register (data) { 94 | return request({ 95 | url: '/user/register', 96 | method: 'post', 97 | data, 98 | }) 99 | } 100 | -------------------------------------------------------------------------------- /src/api/user_token.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function list (params) { 4 | return request({ 5 | url: '/user_token/list', 6 | params, 7 | }) 8 | } 9 | 10 | export function remove (data) { 11 | return request({ 12 | url: '/user_token/delete', 13 | method: 'post', 14 | data, 15 | }) 16 | } 17 | 18 | export function batchRemove (data) { 19 | return request({ 20 | url: '/user_token/batchDelete', 21 | method: 'post', 22 | data, 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /src/assets/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api-web/3084e40039b9421422766299296f8483cbf89561/src/assets/github.png -------------------------------------------------------------------------------- /src/assets/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api-web/3084e40039b9421422766299296f8483cbf89561/src/assets/google.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api-web/3084e40039b9421422766299296f8483cbf89561/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/oidc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api-web/3084e40039b9421422766299296f8483cbf89561/src/assets/oidc.png -------------------------------------------------------------------------------- /src/assets/webauth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api-web/3084e40039b9421422766299296f8483cbf89561/src/assets/webauth.png -------------------------------------------------------------------------------- /src/components/changePwdDialog.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 118 | 119 | 122 | -------------------------------------------------------------------------------- /src/components/form/address.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 96 | 97 | 100 | -------------------------------------------------------------------------------- /src/components/form/upload/imageUpload.vue: -------------------------------------------------------------------------------- 1 | 35 | 148 | 149 | 199 | -------------------------------------------------------------------------------- /src/components/form/upload/local.js: -------------------------------------------------------------------------------- 1 | import { getToken } from '@/utils/auth' 2 | 3 | export function useLocal (beforeUp, host) { 4 | const fileUploadData = {} 5 | const fileUploadHost = host 6 | const headers = { 'api-token': getToken() } 7 | const beforeFileUpload = async (file) => { 8 | if (beforeUp) { 9 | const br = await beforeUp(file) 10 | if (!br) { return Promise.reject() } 11 | } 12 | return Promise.resolve() 13 | } 14 | 15 | return { 16 | fileUploadData, 17 | fileUploadHost, 18 | beforeFileUpload, 19 | headers, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/form/upload/oss.js: -------------------------------------------------------------------------------- 1 | import { ossToken } from '@/api/file' 2 | import { random_filename } from '@/utils/file' 3 | import { reactive, ref } from 'vue' 4 | 5 | export function useOss (beforeUp, multiple) { 6 | let fileUploadData = reactive({ 7 | policy: '', 8 | OSSAccessKeyId: '', 9 | success_action_status: '200', // 让服务端返回200,不然,默认会返回204 10 | callback: '', 11 | signature: '', 12 | 'x:dir': '', 13 | }) 14 | const fileExpire = ref(0) 15 | const fileUploadHost = ref('') 16 | 17 | const beforeFileUpload = async (file) => { 18 | if (beforeUp) { 19 | const br = await beforeUp(file) 20 | if (!br) { return Promise.reject() } 21 | } 22 | 23 | const now = Date.parse(new Date()) / 1000 24 | if (fileExpire.value < now) { 25 | const res = await ossToken() 26 | const obj = JSON.parse(res.data) 27 | fileExpire.value = parseInt(obj['expire']) 28 | fileUploadData.policy = obj['policy'] 29 | fileUploadData.OSSAccessKeyId = obj['accessid'] 30 | fileUploadData.callback = obj['callback'] 31 | fileUploadData.signature = obj['signature'] 32 | fileUploadData['x:dir'] = obj['dir'] 33 | fileUploadHost.value = obj['host'] 34 | } 35 | //多选文件时需要这个,不然每个文件上传的都是一样的data 36 | if (multiple) { 37 | await new Promise(resolve => { 38 | setTimeout(() => { resolve() }, 50) 39 | }) 40 | } 41 | fileUploadData['x:origin_filename'] = file.name 42 | fileUploadData.key = fileUploadData['x:dir'] + random_filename(file.name) 43 | return Promise.resolve() 44 | } 45 | 46 | return { 47 | fileUploadHost, 48 | fileUploadData, 49 | beforeFileUpload, 50 | headers: {}, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/icons/platform.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 29 | 30 | 35 | -------------------------------------------------------------------------------- /src/global.js: -------------------------------------------------------------------------------- 1 | import { ref, reactive, watch } from 'vue' 2 | import { list as fetchUsers } from '@/api/user' 3 | 4 | // todo 缓存所有用户信息 5 | export function loadAllUsers () { 6 | const allUsers = ref([]) 7 | const getAllUsers = async () => { 8 | const res = await fetchUsers({ page_size: 9999 }).catch(_ => false) 9 | if (res) { 10 | allUsers.value = res.data.list 11 | } 12 | } 13 | 14 | return { 15 | allUsers, 16 | getAllUsers, 17 | } 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/layout/components/aside.vue: -------------------------------------------------------------------------------- 1 | 6 | 15 | 16 | 18 | -------------------------------------------------------------------------------- /src/layout/components/header.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 40 | 41 | 70 | 73 | -------------------------------------------------------------------------------- /src/layout/components/menu/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 44 | 45 | 55 | 57 | -------------------------------------------------------------------------------- /src/layout/components/menu/item.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 52 | 53 | 55 | -------------------------------------------------------------------------------- /src/layout/components/setting/index.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 84 | 85 | 113 | -------------------------------------------------------------------------------- /src/layout/components/tags/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 73 | 74 | 84 | -------------------------------------------------------------------------------- /src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 46 | 47 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import 'element-plus/dist/index.css' 3 | import App from './App.vue' 4 | import ElementPlus from 'element-plus' 5 | import zhCn from 'element-plus/es/locale/lang/zh-cn' 6 | import { router } from '@/router' 7 | import 'normalize.css/normalize.css' 8 | import { pinia } from '@/store' 9 | import '@/permission' 10 | import 'element-plus/theme-chalk/dark/css-vars.css' 11 | import '@/styles/style.scss' 12 | import * as ElementIcons from '@element-plus/icons' 13 | 14 | const app = createApp(App) 15 | app.use(ElementPlus, { locale: zhCn }) 16 | app.use(pinia) 17 | app.use(router) 18 | for (let icon in ElementIcons){ 19 | app.component("ElIcon" +icon ,ElementIcons[icon]) 20 | } 21 | app.mount('#app') 22 | -------------------------------------------------------------------------------- /src/permission.js: -------------------------------------------------------------------------------- 1 | import { router } from '@/router' 2 | import { useRouteStore } from '@/store/router' 3 | import { useUserStore } from '@/store/user' 4 | import { getToken } from '@/utils/auth' 5 | import { pinia } from '@/store' 6 | import NProgress from 'nprogress' // progress bar 7 | import 'nprogress/nprogress.css' 8 | import { useAppStore } from '@/store/app' // progress bar style 9 | import { T } from '@/utils/i18n' 10 | 11 | NProgress.configure({ showSpinner: false }) // NProgress Configuration 12 | 13 | const whiteList = ['/login', '/register'] 14 | const routeStore = useRouteStore(pinia) 15 | const appStore = useAppStore(pinia) 16 | appStore.getAdminConfig() 17 | router.beforeEach(async (to, from, next) => { 18 | 19 | document.title = T(to.meta?.title) + ' - ' + appStore.setting.title 20 | NProgress.start() 21 | 22 | const token = getToken() 23 | if (!token) { 24 | //无token,跳转到登录 25 | if (whiteList.indexOf(to.path) !== -1) { 26 | next() 27 | } else { 28 | next(`/login?redirect=${to.path}`) 29 | } 30 | 31 | } else { 32 | //有token 33 | 34 | const userStore = useUserStore(pinia) 35 | 36 | if (!userStore.route_names.length) { 37 | const info = await userStore.info() 38 | if (!info) { 39 | userStore.logout() 40 | next(`/login?redirect=${to.path}`) 41 | } else { 42 | next({ ...to, replace: true }) 43 | } 44 | }/* else if (to.path === '/404') { 45 | next({path: to.redirectedFrom?.fullPath, replace: true}) 46 | }*/ else { 47 | next() 48 | } 49 | } 50 | }) 51 | 52 | router.afterEach(() => { 53 | NProgress.done() 54 | }) 55 | -------------------------------------------------------------------------------- /src/store/app.js: -------------------------------------------------------------------------------- 1 | import { defineStore, acceptHMRUpdate } from 'pinia' 2 | import logo from '@/assets/logo.png' 3 | import zhCn from 'element-plus/es/locale/lang/zh-cn' 4 | import en from 'element-plus/es/locale/lang/en' 5 | import ko from 'element-plus/es/locale/lang/ko' 6 | import ru from 'element-plus/es/locale/lang/ru' 7 | import fr from 'element-plus/es/locale/lang/fr' 8 | import es from 'element-plus/es/locale/lang/es' 9 | import zhTw from 'element-plus/es/locale/lang/zh-tw' 10 | import { admin, app } from '@/api/config' 11 | 12 | const langs = { 13 | 'zh-CN': { name: '中文', value: zhCn, sideBarWidth: '210px' }, 14 | 'en': { name: 'English', value: en, sideBarWidth: '230px' }, 15 | 'fr': { name: 'Français', value: fr, sideBarWidth: '280px' }, 16 | 'ko': { name: '한국어', value: ko, sideBarWidth: '230px' }, 17 | 'ru': { name: 'русский', value: ru, sideBarWidth: '250px' }, 18 | 'es': { name: 'español', value: es, sideBarWidth: '280px' }, 19 | 'zh-TW': { name: '中文繁体', value: zhTw, sideBarWidth: '210px' }, 20 | } 21 | const defaultLang = localStorage.getItem('lang') || navigator.language || 'zh-CN' 22 | export const useAppStore = defineStore({ 23 | id: 'App', 24 | state: () => ({ 25 | setting: { 26 | title: 'Rustdesk API Admin', 27 | hello: '', 28 | sideIsCollapse: false, 29 | logo, 30 | langs: langs, 31 | lang: defaultLang, 32 | locale: langs[defaultLang] ? langs[defaultLang] : langs['en'], 33 | appConfig: { 34 | web_client: 1, 35 | }, 36 | }, 37 | }), 38 | 39 | actions: { 40 | sideCollapse () { 41 | this.setting.sideIsCollapse = !this.setting.sideIsCollapse 42 | }, 43 | setLang (lang) { 44 | console.log('setLang', lang) 45 | this.setting.lang = lang 46 | this.setting.locale = langs[lang] 47 | localStorage.setItem('lang', lang) 48 | }, 49 | changeLang (v) { 50 | this.setLang(v) 51 | }, 52 | loadConfig () { 53 | this.getAppConfig() 54 | this.getAdminConfig() 55 | }, 56 | getAppConfig () { 57 | console.log('getAppConfig') 58 | return app().then(res => { 59 | this.setting.appConfig = res.data 60 | }) 61 | }, 62 | getAdminConfig () { 63 | console.log('getAdminConfig') 64 | return admin().then(res => { 65 | this.replaceAdminTitle(res.data.title) 66 | this.setting.hello = res.data.hello 67 | }) 68 | }, 69 | replaceAdminTitle(newTitle){ 70 | document.title = document.title.replace(`- ${this.setting.title}`, `- ${newTitle}`) 71 | this.setting.title = newTitle 72 | }, 73 | }, 74 | }) 75 | 76 | if (import.meta.hot) { 77 | import.meta.hot.accept(acceptHMRUpdate(useAppStore, import.meta.hot)) 78 | } 79 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia' 2 | 3 | export const pinia = createPinia() 4 | -------------------------------------------------------------------------------- /src/store/router.js: -------------------------------------------------------------------------------- 1 | import { defineStore, acceptHMRUpdate } from 'pinia' 2 | import { lastRoutes, asyncRoutes, router } from '@/router' 3 | 4 | function filterRoute (routes, enableNames) { 5 | return routes.filter(route => { 6 | if (route.children && route.children.length) { 7 | return enableNames.includes(route.name) || route.children.some(r => enableNames.includes(r.name)) 8 | } else { 9 | return enableNames.includes(route.name) 10 | } 11 | }).map(route => { 12 | if (route.children && route.children.length) { 13 | return { 14 | ...route, 15 | children: filterRoute(route.children, enableNames), 16 | } 17 | } else { 18 | return { ...route } 19 | } 20 | }) 21 | } 22 | 23 | export const useRouteStore = defineStore({ 24 | id: 'router', 25 | state: () => ({ 26 | routes: [], 27 | activeRoute: '', 28 | loaded: 0, 29 | keepAlive: [], 30 | }), 31 | actions: { 32 | addRoutes (accessRouteNames) { 33 | if (accessRouteNames.includes('*')) { 34 | this.routes = asyncRoutes 35 | } else { 36 | this.routes = filterRoute(asyncRoutes, accessRouteNames) 37 | } 38 | 39 | this.routes.forEach(route => { 40 | router.addRoute(route) 41 | }) 42 | lastRoutes.forEach(route => { 43 | router.addRoute(route) 44 | }) 45 | this.addKeepAlive(this.routes) 46 | }, 47 | addKeepAlive (route) { 48 | if (route instanceof Array) { 49 | route.forEach(r => { 50 | this.addKeepAlive(r) 51 | }) 52 | } else if (route.children && route.children.length) { 53 | this.addKeepAlive(route.children) 54 | } else if (route.meta?.keepAlive && !this.keepAlive.includes(route.name)) { 55 | this.keepAlive.push(route.name) 56 | } 57 | }, 58 | 59 | }, 60 | }) 61 | 62 | if (import.meta.hot) { 63 | import.meta.hot.accept(acceptHMRUpdate(useRouteStore, import.meta.hot)) 64 | } 65 | -------------------------------------------------------------------------------- /src/store/tags.js: -------------------------------------------------------------------------------- 1 | import { defineStore, acceptHMRUpdate } from 'pinia' 2 | 3 | export const useTagsStore = defineStore({ 4 | id: 'tags', 5 | state: () => ({ 6 | tags: [], 7 | cached: [], 8 | }), 9 | actions: { 10 | initTags () { 11 | // this.tags.push( 12 | // { 13 | // name: 'Home', 14 | // path: '/Home', 15 | // title: '首页', 16 | // active: false, 17 | // closeable: false, 18 | // keepAlive: false, 19 | // }) 20 | }, 21 | addTag (route) { 22 | const tags = this.tags 23 | if (tags.find(t => t.name === route.name)) { 24 | tags.forEach(t => t.active = false) 25 | tags.find(t => t.name === route.name).active = true 26 | } else { 27 | tags.forEach(t => t.active = false) 28 | if (route.meta?.keepAlive) { 29 | this.addCachedTag(route.name) 30 | } 31 | tags.push({ 32 | name: route.name, 33 | path: route.fullPath, 34 | title: route.meta?.title || route.name, 35 | active: true, 36 | closeable: true, 37 | keepAlive: route.meta?.keepAlive, 38 | }) 39 | 40 | } 41 | this.$patch({ tags }) 42 | }, 43 | removeTag (tag) { 44 | let tags = this.tags 45 | if (tags.find(t => t.name === tag.name)) { 46 | const index = tags.findIndex(t => t.name === tag.name) 47 | if (index > -1) { 48 | if (tags[index].keepAlive) { 49 | this.removeCachedTag(tags[index].name) 50 | } 51 | tags.splice(index, 1) 52 | } 53 | } 54 | this.$patch({ tags }) 55 | }, 56 | addCachedTag (name) { 57 | if (!this.cached.includes(name)) { 58 | this.cached.push(name) 59 | } 60 | }, 61 | removeCachedTag (name) { 62 | if (this.cached.includes(name)) { 63 | this.cached.splice(this.cached.indexOf(name), 1) 64 | } 65 | 66 | }, 67 | 68 | }, 69 | }) 70 | 71 | if (import.meta.hot) { 72 | import.meta.hot.accept(acceptHMRUpdate(useTagsStore, import.meta.hot)) 73 | } 74 | -------------------------------------------------------------------------------- /src/store/user.js: -------------------------------------------------------------------------------- 1 | import { defineStore, acceptHMRUpdate } from 'pinia' 2 | import { current, login } from '@/api/user' 3 | import { setToken, removeToken, setCode, removeCode } from '@/utils/auth' 4 | import { useRouteStore } from '@/store/router' 5 | import { useAppStore } from '@/store/app' 6 | import { oidcAuth, oidcQuery } from '@/api/login' 7 | 8 | export const useUserStore = defineStore({ 9 | id: 'user', 10 | state: () => ({ 11 | nickname: '', 12 | username: '', 13 | email: '', 14 | token: '', 15 | role: '', 16 | avatar: '', 17 | route_names: [], 18 | }), 19 | 20 | actions: { 21 | logout () { 22 | removeToken() 23 | removeCode() 24 | this.$patch({ 25 | name: '', 26 | role: {}, 27 | }) 28 | }, 29 | 30 | saveUserData (userData) { 31 | // useAppStore().getAppConfig() 32 | setToken(userData.token) 33 | // 34 | localStorage.setItem('user_info', JSON.stringify({ name: userData.username })) 35 | this.$patch({ 36 | ...userData, 37 | }) 38 | if (userData.route_names && userData.route_names.length) { 39 | useRouteStore().addRoutes(userData.route_names) 40 | } 41 | }, 42 | 43 | async login (form) { 44 | const res = await login(form).catch(e => e) 45 | console.log('login', res) 46 | if (!res.code) { 47 | useAppStore().loadConfig() 48 | const userData = res.data 49 | this.saveUserData(userData) 50 | return userData 51 | } else { 52 | return Promise.reject(res) 53 | } 54 | }, 55 | async info () { 56 | const res = await current().catch(_ => false) 57 | if (res) { 58 | useAppStore().loadConfig() 59 | const userData = res.data 60 | setToken(userData.token) 61 | this.$patch({ 62 | ...userData, 63 | }) 64 | useRouteStore().addRoutes(userData.route_names) 65 | return userData 66 | } 67 | return false 68 | }, 69 | async oidc (provider, platform, browser) { 70 | // oidc data need to be implement 71 | const data = { 72 | deviceInfo: { 73 | name: navigator.userAgent, // 使用浏览器的 User-Agent 作为设备名 74 | os: platform, // 获取操作系统信息 75 | type: 'webadmin', // any vaule 76 | }, 77 | id: `${platform}-${browser}`, 78 | op: provider, // 传入的 provider 79 | uuid: '',//crypto.randomUUID(), // 自动生成 UUID 80 | } 81 | const res = await oidcAuth(data).catch(_ => false) 82 | if (res) { 83 | const { code, url } = res.data 84 | setCode(code) 85 | if (provider == 'webauth') { 86 | window.open(url) 87 | } else { 88 | window.location.href = url 89 | } 90 | } 91 | }, 92 | async query (code) { 93 | const params = { 'code': code, uuid: '' } 94 | const res = await oidcQuery(params).catch(_ => false) 95 | if (res) { 96 | removeCode() 97 | useAppStore().loadConfig() 98 | const userData = res.data 99 | this.saveUserData(userData) 100 | return userData 101 | } 102 | return false 103 | }, 104 | }, 105 | }) 106 | 107 | if (import.meta.hot) { 108 | import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot)) 109 | } 110 | -------------------------------------------------------------------------------- /src/styles/style.scss: -------------------------------------------------------------------------------- 1 | $basicBlack: #000000; 2 | $basicWhite: #ffffff; 3 | 4 | $primaryColor: #409eff; 5 | 6 | :root { 7 | --basicBlack: #000000; 8 | --basicWhite: #ffffff; 9 | --primaryColor: #409eff; 10 | --tag-bg-color: #efefef; 11 | } 12 | 13 | .list-body { 14 | margin: 10px 0; 15 | } 16 | 17 | .dialog-form { 18 | max-width: 600px; 19 | margin: 20px auto; 20 | } 21 | 22 | .list-query { 23 | .el-select { 24 | --el-select-width: 160px; 25 | } 26 | 27 | .el-input { 28 | --el-input-width: 160px; 29 | } 30 | } 31 | 32 | .table-actions { 33 | .el-button { 34 | margin-top: 5px; 35 | margin-bottom: 5px; 36 | margin-left: 5px; 37 | margin-right: 5px; 38 | } 39 | } 40 | 41 | html.dark { 42 | /* 自定义深色背景颜色 */ 43 | //--el-bg-color: #626aef; 44 | --tag-bg-color: #24252b; 45 | --basicBlack: #fff; 46 | } 47 | -------------------------------------------------------------------------------- /src/utils/auth.js: -------------------------------------------------------------------------------- 1 | const TokenKey = 'access_token' 2 | const OidcCode = 'oidc_code' 3 | const OidcCodeExpiry = 'oidc_code_expiry'; 4 | 5 | export function getToken () { 6 | return localStorage.getItem(TokenKey) 7 | } 8 | 9 | export function setToken (token) { 10 | localStorage.setItem(`wc-option:local:access_token`, token) 11 | return localStorage.setItem(TokenKey, token) 12 | } 13 | 14 | export function removeToken () { 15 | return localStorage.removeItem(TokenKey) 16 | } 17 | 18 | // 设置 code,并存储当前时间戳(单位:毫秒) 19 | export function setCode(code) { 20 | const now = Date.now(); // 当前时间戳(毫秒) 21 | const expiry = now + 60 * 1000; // 60 秒后过期 22 | 23 | localStorage.setItem(OidcCode, code); // 存储 code 24 | localStorage.setItem(OidcCodeExpiry, expiry); // 存储过期时间戳 25 | } 26 | 27 | // 获取 code,如果已过期则删除并返回 null 28 | export function getCode() { 29 | const expiry = localStorage.getItem(OidcCodeExpiry); // 获取过期时间戳 30 | const now = Date.now(); // 当前时间戳 31 | 32 | if (expiry && now > parseInt(expiry)) { 33 | // 如果已过期,删除 code 和过期时间 34 | removeCode(); 35 | return null; 36 | } 37 | return localStorage.getItem(OidcCode); // 返回 code(如果未过期) 38 | } 39 | 40 | // 删除 code 和过期时间 41 | export function removeCode() { 42 | localStorage.removeItem(OidcCode); 43 | localStorage.removeItem(OidcCodeExpiry); 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/clipboard.js: -------------------------------------------------------------------------------- 1 | import Clipboard from 'clipboard' 2 | import { ElMessage } from 'element-plus' 3 | import { T } from '@/utils/i18n' 4 | 5 | export function handleClipboard (text, event) { 6 | const clipboard = new Clipboard(event.target.toString(), { 7 | text: () => text, 8 | }) 9 | clipboard.on('success', () => { 10 | ElMessage.success(T('CopySuccess')) 11 | clipboard.destroy() 12 | }) 13 | clipboard.on('error', () => { 14 | ElMessage.error(T('CopyFailed')) 15 | clipboard.destroy() 16 | }) 17 | clipboard.onClick(event) 18 | } 19 | 20 | export function copyImage (targetNode) { 21 | if (window.getSelection) { 22 | // chrome等主流浏览器 23 | var selection = window.getSelection() 24 | selection.removeAllRanges() 25 | var range = document.createRange() 26 | range.selectNode(targetNode) 27 | selection.addRange(range) 28 | } else if (document.body.createTextRange) { 29 | console.log('IE') 30 | // ie 31 | const range = document.body.createTextRange() 32 | range.moveToElementText(targetNode) 33 | range.select() 34 | } 35 | document.execCommand('copy') 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/common_options.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const ENABLE_STATUS = 1 4 | export const DISABLE_STATUS = 2 5 | -------------------------------------------------------------------------------- /src/utils/file.js: -------------------------------------------------------------------------------- 1 | export function get_suffix (filename) { 2 | var pos = filename.lastIndexOf('.') 3 | var suffix = '' 4 | if (pos !== -1) { 5 | suffix = filename.substring(pos) 6 | } 7 | return suffix 8 | } 9 | 10 | export function random_string (len) { 11 | len = len || 32 12 | var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678' 13 | var maxPos = chars.length 14 | var pwd = '' 15 | for (let i = 0; i < len; i++) { 16 | pwd += chars.charAt(Math.floor(Math.random() * maxPos)) 17 | } 18 | return pwd 19 | } 20 | 21 | export function random_filename (filename) { 22 | var suffix = get_suffix(filename) 23 | var time = new Date() 24 | var time2 = new Date('2020/01/01') 25 | return Math.ceil((time.getTime() - time2.getTime()) / 1000) + '_' + random_string(10) + suffix 26 | } 27 | 28 | export function utf8_to_b64 (str) { 29 | return window.btoa(unescape(encodeURIComponent(str))) 30 | } 31 | 32 | export function b64_to_utf8 (str) { 33 | return decodeURIComponent(escape(window.atob(str))) 34 | } 35 | 36 | export function jsonToCsv (data) { 37 | let csv = '' 38 | let keys = Object.keys(data[0]) 39 | csv += keys.join(',') + '\n' 40 | data.forEach(row => { 41 | csv += keys.map(key => `"${row[key]}"`).join(',') + '\n' 42 | }) 43 | return new Blob([csv], { type: 'text/csv' }) 44 | } 45 | 46 | export function downBlob (blob, filename) { 47 | const url = window.URL.createObjectURL(blob) 48 | const a = document.createElement('a') 49 | a.href = url 50 | a.download = filename 51 | document.body.appendChild(a) 52 | a.click() 53 | setTimeout(() => { 54 | window.URL.revokeObjectURL(url) 55 | document.body.removeChild(a) 56 | }) 57 | } 58 | 59 | export function sizeFormat (size) { 60 | if (size < 1024) { 61 | return size + 'B' 62 | } else if (size < 1024 * 1024) { 63 | return (size / 1024).toFixed(2) + 'KB' 64 | } else if (size < 1024 * 1024 * 1024) { 65 | return (size / 1024 / 1024).toFixed(2) + 'MB' 66 | } else { 67 | return (size / 1024 / 1024 / 1024).toFixed(2) + 'GB' 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/utils/i18n.js: -------------------------------------------------------------------------------- 1 | import en from '@/utils/i18n/en.json' 2 | import fr from '@/utils/i18n/fr.json' 3 | import zhCN from '@/utils/i18n/zh_CN.json' 4 | import ko from '@/utils/i18n/ko.json' 5 | import ru from '@/utils/i18n/ru.json' 6 | import es from '@/utils/i18n/es.json' 7 | import zhTW from '@/utils/i18n/zh_TW.json' 8 | import { useAppStore } from '@/store/app' 9 | 10 | const trans = { 11 | 'en': en, 12 | 'fr': fr, 13 | 'zh-CN': zhCN, 14 | 'ko': ko, 15 | 'ru': ru, 16 | 'es': es, 17 | 'zh-TW': zhTW, 18 | } 19 | export function T (key, params, num = 0) { 20 | const appStore = useAppStore() 21 | const lang = appStore.setting.lang 22 | const tran = trans[lang]?.[key] 23 | if (!tran) { 24 | return key 25 | } 26 | const msg = num > 1 ? (tran.Other ? tran.Other : tran.One) : tran.One 27 | //msg 是这样 {name} is name 28 | //params 是这样 {name: 'zhangsan'} 29 | //替换 30 | return msg.replace(/{(\w+)}/g, function (match, key) { 31 | return params[key] || match 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/peer.js: -------------------------------------------------------------------------------- 1 | export const connectByClient = (id) => { 2 | //不新开窗口打开url protocol ,格式是 rustdesk:// 3 | // window.open(`rustdesk://${row.id}`) 4 | let a = document.createElement('a') 5 | a.href = `rustdesk://${id}` 6 | a.target = '_self' 7 | a.click() 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { ElMessage } from 'element-plus' 3 | import { getToken, removeToken } from '@/utils/auth' 4 | import { useUserStore } from '@/store/user' 5 | import { pinia } from '@/store' 6 | import { useAppStore } from '@/store/app' 7 | 8 | // create an axios instance 9 | const service = axios.create({ 10 | baseURL: import.meta.env.VITE_SERVER_API, 11 | withCredentials: true, // send cookies when cross-domain requests 12 | timeout: 50000, // request timeout 13 | }) 14 | 15 | // request interceptor 16 | service.interceptors.request.use( 17 | config => { 18 | if (!config.headers) { 19 | config.headers = {} 20 | } 21 | const userStore = useUserStore(pinia) 22 | 23 | const token = userStore.token || getToken() 24 | if (token) { 25 | config.headers['api-token'] = token 26 | } 27 | 28 | const app = useAppStore() 29 | const lang = app.setting.lang 30 | if (lang) { 31 | // console.log('lang', lang) 32 | config.headers['Accept-Language'] = lang 33 | } 34 | 35 | return config 36 | }, 37 | error => { 38 | // do something with request error 39 | return Promise.reject(error) 40 | }, 41 | ) 42 | 43 | // response interceptor 44 | service.interceptors.response.use( 45 | /** 46 | * If you want to get http information such as headers or status 47 | * Please return response => response 48 | */ 49 | 50 | /** 51 | * Determine the request status by custom code 52 | * Here is just an example 53 | * You can also judge the status by HTTP Status Code 54 | */ 55 | response => { 56 | const res = response.data 57 | 58 | // for the endpoint /login-options 59 | // I'm not sure if this is a good idea 60 | if (Array.isArray(res)) { 61 | return res; 62 | } 63 | 64 | // if the custom code is not 20000, it is judged as an error. 65 | if (res.code !== 0) { 66 | ElMessage({ 67 | message: res.message || 'error', 68 | type: 'error', 69 | duration: 5 * 1000, 70 | }) 71 | 72 | if (res.code === 403) { 73 | removeToken() 74 | window.location.reload() 75 | } 76 | return Promise.reject(res) 77 | } else { 78 | return res 79 | } 80 | }, 81 | error => { 82 | if (error.code === 'ECONNABORTED' 83 | && error.message.indexOf('timeout') > -1) { 84 | error.message = 'Connection Time Out!' 85 | } 86 | ElMessage({ 87 | message: error.message, 88 | type: 'error', 89 | duration: 5 * 1000, 90 | }) 91 | return Promise.reject(error) 92 | }, 93 | ) 94 | 95 | export default service 96 | -------------------------------------------------------------------------------- /src/utils/time.js: -------------------------------------------------------------------------------- 1 | import { T } from '@/utils/i18n' 2 | 3 | export function timeAgo (time) { 4 | let now = new Date().getTime() 5 | let after = new Date(time).getTime() 6 | let dis = now - after 7 | if (dis < 60 * 1000) { 8 | return T('JustNow') 9 | } else if (dis < 60 * 60 * 1000) { 10 | const num = Math.floor(dis / (60 * 1000)) 11 | return T('MinutesAgo', { param: num }, num) 12 | } else if (dis < 24 * 60 * 60 * 1000) { 13 | const num = Math.floor(dis / (60 * 60 * 1000)) 14 | return T('HoursAgo', { param: num }, num) 15 | } else if (dis < 30 * 24 * 60 * 60 * 1000) { 16 | const num = Math.floor(dis / (24 * 60 * 60 * 1000)) 17 | return T('DaysAgo', { param: num }, num) 18 | } else if (dis < 12 * 30 * 24 * 60 * 60 * 1000) { 19 | const num = Math.floor(dis / (30 * 24 * 60 * 60 * 1000)) 20 | return T('MonthsAgo', { param: num }, num) 21 | } else { 22 | const num = Math.floor(dis / (12 * 30 * 24 * 60 * 60 * 1000)) 23 | return T('YearsAgo', { param: num }, num) 24 | } 25 | } 26 | 27 | export function formatTime (unix, format = 'yyyy-MM-dd hh:mm:ss') { 28 | let date = new Date(unix) 29 | let o = { 30 | 'M+': date.getMonth() + 1, 31 | 'd+': date.getDate(), 32 | 'h+': date.getHours(), 33 | 'm+': date.getMinutes(), 34 | 's+': date.getSeconds(), 35 | 'q+': Math.floor((date.getMonth() + 3) / 3), 36 | S: date.getMilliseconds(), 37 | } 38 | if (/(y+)/.test(format)) { 39 | format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) 40 | } 41 | for (let k in o) { 42 | if (new RegExp('(' + k + ')').test(format)) { 43 | format = format.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)) 44 | } 45 | } 46 | return format 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/webclient.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import { server } from '@/api/config' 3 | import Websock from '@/utils/webclient/websock' 4 | import * as rendezvous from '@/utils/webclient/rendezvous' 5 | import * as message from '@/utils/webclient/message' 6 | import { ElMessageBox } from 'element-plus' 7 | import { T } from '@/utils/i18n' 8 | 9 | const prefix = 'wc-' 10 | 11 | export const toWebClientLink = (row) => { 12 | //v2 13 | window.open(`${rustdeskConfig.value.api_server}/webclient2/#/${row.id}`) 14 | } 15 | 16 | export const rustdeskConfig = ref({}) 17 | 18 | export async function loadRustdeskConfig () { 19 | console.log('loadRustdeskConfig') 20 | if (rustdeskConfig.value.id_server === undefined || rustdeskConfig.value.key === undefined) { 21 | const res = await server().catch(_ => false) 22 | if (res) { 23 | rustdeskConfig.value = res.data 24 | localStorage.setItem(`${prefix}custom-rendezvous-server`, res.data.id_server) 25 | localStorage.setItem(`${prefix}key`, res.data.key) 26 | localStorage.setItem(`${prefix}api-server`, res.data.api_server) 27 | } 28 | } 29 | return { 30 | rustdeskConfig, 31 | } 32 | } 33 | 34 | loadRustdeskConfig() 35 | 36 | export async function getPeerSlat (id) { 37 | const [addr, port] = rustdeskConfig.value.id_server.split(':') 38 | if (!addr) { 39 | return 40 | } 41 | const scheme = location.protocol === 'https:' ? 'wss' : 'ws' 42 | const ws = new Websock(`${scheme}://${addr}:21118`, true) 43 | await ws.open() 44 | const conn_type = rendezvous.ConnType.DEFAULT_CONN 45 | const nat_type = rendezvous.NatType.SYMMETRIC 46 | const punch_hole_request = rendezvous.PunchHoleRequest.fromPartial({ 47 | id, 48 | licence_key: rustdeskConfig.value.key || undefined, 49 | conn_type, 50 | nat_type, 51 | token: undefined, 52 | }) 53 | ws.sendRendezvous({ punch_hole_request }) 54 | //rendezvous.RendezvousMessage 55 | const msg = (await ws.next()) 56 | ws.close() 57 | console.log(new Date() + ': Got relay response', msg) 58 | const phr = msg.punch_hole_response 59 | const rr = msg.relay_response 60 | if (phr) { 61 | if (phr?.other_failure) { 62 | this.msgbox('error', 'Error', phr?.other_failure) 63 | return 64 | } 65 | if (phr.failure != rendezvous.PunchHoleResponse_Failure.UNRECOGNIZED) { 66 | switch (phr?.failure) { 67 | case rendezvous.PunchHoleResponse_Failure.ID_NOT_EXIST: 68 | ElMessageBox.alert(T('IDNotExist'), T('Error')) 69 | break 70 | case rendezvous.PunchHoleResponse_Failure.OFFLINE: 71 | ElMessageBox.alert(T('RemoteDesktopOffline'), T('Error')) 72 | break 73 | case rendezvous.PunchHoleResponse_Failure.LICENSE_MISMATCH: 74 | ElMessageBox.alert(T('KeyMismatch'), T('Error')) 75 | break 76 | case rendezvous.PunchHoleResponse_Failure.LICENSE_OVERUSE: 77 | ElMessageBox.alert(T('KeyOveruse'), T('Error')) 78 | break 79 | } 80 | } 81 | return false 82 | } else if (rr) { 83 | const uuid = rr.uuid 84 | console.log(new Date() + ': Connecting to relay server') 85 | 86 | const _ws = new Websock(`${scheme}://${addr}:21119`, false) 87 | await _ws.open() 88 | console.log(new Date() + ': Connected to relay server') 89 | const request_relay = rendezvous.RequestRelay.fromPartial({ 90 | licence_key: rustdeskConfig.value.key || undefined, 91 | uuid, 92 | }) 93 | _ws.sendRendezvous({ request_relay }) 94 | 95 | //暂不支持pk 96 | const public_key = message.PublicKey.fromPartial({}) 97 | _ws?.sendMessage({ public_key }) 98 | // const secure = (await this.secure(pk)) || false; 99 | // globals.pushEvent("connection_ready", { secure, direct: false }); 100 | while (true) { 101 | const msg = (await _ws?.next()) 102 | console.log('msg', msg) 103 | if (msg?.hash) { 104 | console.log('hash msg.....', msg.hash) 105 | _ws.close() 106 | return msg.hash 107 | } 108 | } 109 | return false 110 | } 111 | 112 | } 113 | 114 | export function getV2ShareUrl (token) { 115 | return `${rustdeskConfig.value.api_server}/webclient2/#/?share_token=${token}` 116 | } 117 | -------------------------------------------------------------------------------- /src/utils/webclient/websock.ts: -------------------------------------------------------------------------------- 1 | import * as message from "./message.js"; 2 | import * as rendezvous from "./rendezvous.js"; 3 | 4 | type Keys = "message" | "open" | "close" | "error"; 5 | 6 | export default class Websock { 7 | _websocket: WebSocket; 8 | _eventHandlers: { [key in Keys]: Function }; 9 | _buf: (rendezvous.RendezvousMessage | message.Message)[]; 10 | _status: any; 11 | _latency: number; 12 | _secretKey: [Uint8Array, number, number] | undefined; 13 | _uri: string; 14 | _isRendezvous: boolean; 15 | 16 | constructor(uri: string, isRendezvous: boolean = true) { 17 | this._eventHandlers = { 18 | message: (_: any) => {}, 19 | open: () => {}, 20 | close: () => {}, 21 | error: () => {}, 22 | }; 23 | this._uri = uri; 24 | this._status = ""; 25 | this._buf = []; 26 | this._websocket = new WebSocket(uri); 27 | this._websocket.onmessage = this._recv_message.bind(this); 28 | this._websocket.binaryType = "arraybuffer"; 29 | this._latency = new Date().getTime(); 30 | this._isRendezvous = isRendezvous; 31 | } 32 | 33 | latency(): number { 34 | return this._latency; 35 | } 36 | 37 | setSecretKey(key: Uint8Array) { 38 | this._secretKey = [key, 0, 0]; 39 | } 40 | 41 | sendMessage(json: message.DeepPartial) { 42 | let data = message.Message.encode( 43 | message.Message.fromPartial(json) 44 | ).finish(); 45 | // let k = this._secretKey; 46 | // if (k) { 47 | // k[1] += 1; 48 | // data = globals.encrypt(data, k[1], k[0]); 49 | // } 50 | this._websocket.send(data); 51 | } 52 | 53 | sendRendezvous(data: rendezvous.DeepPartial) { 54 | this._websocket.send( 55 | rendezvous.RendezvousMessage.encode( 56 | rendezvous.RendezvousMessage.fromPartial(data) 57 | ).finish() 58 | ); 59 | } 60 | 61 | parseMessage(data: Uint8Array) { 62 | return message.Message.decode(data); 63 | } 64 | 65 | parseRendezvous(data: Uint8Array) { 66 | return rendezvous.RendezvousMessage.decode(data); 67 | } 68 | 69 | // Event Handlers 70 | off(evt: Keys) { 71 | this._eventHandlers[evt] = () => {}; 72 | } 73 | 74 | on(evt: Keys, handler: Function) { 75 | this._eventHandlers[evt] = handler; 76 | } 77 | 78 | async open(timeout: number = 12000): Promise { 79 | return new Promise((resolve, reject) => { 80 | setTimeout(() => { 81 | if (this._status != "open") { 82 | reject(this._status || "Timeout"); 83 | } 84 | }, timeout); 85 | this._websocket.onopen = () => { 86 | this._latency = new Date().getTime() - this._latency; 87 | this._status = "open"; 88 | console.debug(">> WebSock.onopen"); 89 | if (this._websocket?.protocol) { 90 | console.info( 91 | "Server choose sub-protocol: " + this._websocket.protocol 92 | ); 93 | } 94 | 95 | this._eventHandlers.open(); 96 | console.info("WebSock.onopen"); 97 | resolve(this); 98 | }; 99 | this._websocket.onclose = (e) => { 100 | if (this._status == "open") { 101 | // e.code 1000 means that the connection was closed normally. 102 | // 103 | } 104 | this._status = e; 105 | console.error("WebSock.onclose: "); 106 | console.error(e); 107 | this._eventHandlers.close(e); 108 | reject("Reset by the peer"); 109 | }; 110 | this._websocket.onerror = (e: any) => { 111 | if (!this._status) { 112 | reject("Failed to connect to " + (this._isRendezvous ? "rendezvous" : "relay") + " server"); 113 | return; 114 | } 115 | this._status = e; 116 | console.error("WebSock.onerror: ") 117 | console.error(e); 118 | this._eventHandlers.error(e); 119 | }; 120 | }); 121 | } 122 | 123 | async next( 124 | timeout = 12000 125 | ): Promise { 126 | const func = ( 127 | resolve: (value: rendezvous.RendezvousMessage | message.Message) => void, 128 | reject: (reason: any) => void, 129 | tm0: number 130 | ) => { 131 | // console.log('next') 132 | if (this._buf.length) { 133 | resolve(this._buf[0]); 134 | this._buf.splice(0, 1); 135 | } else { 136 | if (this._status != "open") { 137 | reject(this._status); 138 | return; 139 | } 140 | if (new Date().getTime() > tm0 + timeout) { 141 | reject("Timeout"); 142 | } else { 143 | setTimeout(() => func(resolve, reject, tm0), 1); 144 | } 145 | } 146 | }; 147 | return new Promise((resolve, reject) => { 148 | func(resolve, reject, new Date().getTime()); 149 | }); 150 | } 151 | 152 | close() { 153 | this._status = ""; 154 | if (this._websocket) { 155 | if ( 156 | this._websocket.readyState === WebSocket.OPEN || 157 | this._websocket.readyState === WebSocket.CONNECTING 158 | ) { 159 | console.info("Closing WebSocket connection"); 160 | this._websocket.close(); 161 | } 162 | 163 | this._websocket.onmessage = () => {}; 164 | } 165 | } 166 | 167 | _recv_message(e: any) { 168 | if (e.data instanceof window.ArrayBuffer) { 169 | let bytes = new Uint8Array(e.data); 170 | // const k = this._secretKey; 171 | // if (k) { 172 | // k[2] += 1; 173 | // bytes = globals.decrypt(bytes, k[2], k[0]); 174 | // } 175 | this._buf.push( 176 | this._isRendezvous 177 | ? this.parseRendezvous(bytes) 178 | : this.parseMessage(bytes) 179 | ); 180 | } 181 | this._eventHandlers.message(e.data); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/views/address_book/collection.js: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from 'vue' 2 | import { list as admin_list, create as admin_create, update as admin_update, remove as admin_remove } from '@/api/address_book_collection' 3 | import { list as my_list, create as my_create, update as my_update, remove as my_remove } from '@/api/my/address_book_collection' 4 | import { ElMessage, ElMessageBox } from 'element-plus' 5 | import { T } from '@/utils/i18n' 6 | 7 | const apis = { 8 | admin: { list: admin_list, remove: admin_remove, update: admin_update, create: admin_create }, 9 | my: { list: my_list, remove: my_remove, create: my_create, update: my_update }, 10 | } 11 | 12 | export function useRepositories (api_type = 'my') { 13 | const listRes = reactive({ 14 | list: [], total: 0, loading: false, 15 | }) 16 | const listQuery = reactive({ 17 | page: 1, 18 | page_size: 10, 19 | name: null, 20 | user_id: null, 21 | }) 22 | 23 | const getList = async () => { 24 | listRes.loading = true 25 | const res = await apis[api_type].list(listQuery).catch(_ => false) 26 | listRes.loading = false 27 | if (res) { 28 | listRes.list = res.data.list 29 | listRes.total = res.data.total 30 | } 31 | } 32 | const handlerQuery = () => { 33 | if (listQuery.page === 1) { 34 | getList() 35 | } else { 36 | listQuery.page = 1 37 | } 38 | } 39 | 40 | const del = async (row) => { 41 | const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('Delete') }), { 42 | confirmButtonText: T('Confirm'), 43 | cancelButtonText: T('Cancel'), 44 | type: 'warning', 45 | }).catch(_ => false) 46 | if (!cf) { 47 | return false 48 | } 49 | 50 | const res = await apis[api_type].remove({ id: row.id }).catch(_ => false) 51 | if (res) { 52 | ElMessage.success(T('OperationSuccess')) 53 | getList() 54 | } 55 | } 56 | 57 | const formVisible = ref(false) 58 | const formData = reactive({ 59 | id: 0, 60 | name: '', 61 | }) 62 | 63 | const toEdit = (row) => { 64 | formVisible.value = true 65 | //将row中的数据赋值给formData 66 | Object.keys(formData).forEach(key => { 67 | formData[key] = row[key] 68 | }) 69 | 70 | } 71 | const toAdd = () => { 72 | formVisible.value = true 73 | //重置formData 74 | Object.keys(formData).forEach(key => { 75 | formData[key] = undefined 76 | }) 77 | 78 | } 79 | const submit = async () => { 80 | const api = formData.id ? apis[api_type].update : apis[api_type].create 81 | const res = await api(formData).catch(_ => false) 82 | if (res) { 83 | ElMessage.success(T('OperationSuccess')) 84 | formVisible.value = false 85 | getList() 86 | } 87 | } 88 | return { 89 | listRes, 90 | listQuery, 91 | getList, 92 | handlerQuery, 93 | del, 94 | formVisible, 95 | formData, 96 | toEdit, 97 | toAdd, 98 | submit, 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/views/address_book/collection.vue: -------------------------------------------------------------------------------- 1 | 77 | 78 | 119 | 120 | 123 | -------------------------------------------------------------------------------- /src/views/address_book/components/shareByWebClient.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 145 | 146 | 149 | -------------------------------------------------------------------------------- /src/views/address_book/rule.js: -------------------------------------------------------------------------------- 1 | import { computed, reactive, ref } from 'vue' 2 | import { list as admin_list, create as admin_create, update as admin_update, remove as admin_remove } from '@/api/address_book_collection_rule' 3 | import { list as my_list, create as my_create, update as my_update, remove as my_remove } from '@/api/my/address_book_collection_rule' 4 | import { groupUsers } from '@/api/user' 5 | import { ElMessage, ElMessageBox } from 'element-plus' 6 | import { T } from '@/utils/i18n' 7 | 8 | const apis = { 9 | admin: { list: admin_list, remove: admin_remove, update: admin_update, create: admin_create }, 10 | my: { list: my_list, remove: my_remove, create: my_create, update: my_update }, 11 | } 12 | 13 | export function useRepositories (api_type = 'my') { 14 | const listRes = reactive({ 15 | list: [], total: 0, loading: false, 16 | }) 17 | const listQuery = reactive({ 18 | page: 1, 19 | page_size: 10, 20 | collection_id: null, 21 | }) 22 | 23 | const getList = async () => { 24 | listRes.loading = true 25 | const res = await apis[api_type].list(listQuery).catch(_ => false) 26 | listRes.loading = false 27 | if (res) { 28 | listRes.list = res.data.list 29 | listRes.total = res.data.total 30 | } 31 | } 32 | const handlerQuery = () => { 33 | if (listQuery.page === 1) { 34 | getList() 35 | } else { 36 | listQuery.page = 1 37 | } 38 | } 39 | 40 | const del = async (row) => { 41 | const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('Delete') }), { 42 | confirmButtonText: T('Confirm'), 43 | cancelButtonText: T('Cancel'), 44 | type: 'warning', 45 | }).catch(_ => false) 46 | if (!cf) { 47 | return false 48 | } 49 | 50 | const res = await apis[api_type].remove({ id: row.id }).catch(_ => false) 51 | if (res) { 52 | ElMessage.success(T('OperationSuccess')) 53 | getList() 54 | } 55 | } 56 | 57 | const rules = computed(_ => [ 58 | { label: T('Read'), value: 1 }, 59 | { label: T('ReadWrite'), value: 2 }, 60 | { label: T('FullControl'), value: 3 }, 61 | ]) 62 | const TYPE_U = 1 63 | const TYPE_G = 2 64 | const types = computed(_ => [ 65 | { label: T('Group'), value: TYPE_G }, 66 | { label: T('User'), value: TYPE_U }, 67 | ]) 68 | const formVisible = ref(false) 69 | const formData = reactive({ 70 | id: 0, 71 | collection_id: null, 72 | type: TYPE_U, 73 | rule: 1, 74 | g_id: null, 75 | u_id: null, 76 | to_id: null, 77 | user_id: null, 78 | }) 79 | 80 | const toEdit = (row) => { 81 | formVisible.value = true 82 | //将row中的数据赋值给formData 83 | Object.keys(formData).forEach(key => { 84 | formData[key] = row[key] 85 | }) 86 | if (row.type === TYPE_U) { 87 | formData.u_id = row.to_id 88 | formData.g_id = users.value.find(u => u.id === row.to_id)?.group_id 89 | } else { 90 | formData.g_id = row.to_id 91 | formData.u_id = null 92 | } 93 | } 94 | const toAdd = () => { 95 | //初始化formData 96 | formData.id = 0 97 | formData.type = TYPE_U 98 | formData.rule = 1 99 | formData.g_id = null 100 | formData.u_id = null 101 | 102 | formVisible.value = true 103 | 104 | } 105 | const submit = async () => { 106 | const api = formData.id ? apis[api_type].update : apis[api_type].create 107 | const form = { 108 | ...formData, 109 | } 110 | form.to_id = form.type === TYPE_G ? form.g_id : form.u_id 111 | const res = await api(form).catch(_ => false) 112 | if (res) { 113 | ElMessage.success(T('OperationSuccess')) 114 | formVisible.value = false 115 | getList() 116 | } 117 | } 118 | const groups = ref([]) 119 | const users = ref([]) 120 | const getGroupUsers = async () => { 121 | const res = await groupUsers().catch(_ => false) 122 | if (res) { 123 | groups.value = res.data.groups.map(item => { 124 | if (!item.children) { 125 | item.children = [] 126 | } 127 | res.data.users.map(u => { 128 | if (item.id === u.group_id) { 129 | item.children.push(u) 130 | } 131 | }) 132 | return item 133 | }) 134 | users.value = res.data.users 135 | } 136 | } 137 | const changeGId = () => { 138 | formData.u_id = null 139 | } 140 | return { 141 | listRes, 142 | listQuery, 143 | getList, 144 | handlerQuery, 145 | del, 146 | formVisible, 147 | formData, 148 | toEdit, 149 | toAdd, 150 | submit, 151 | rules, 152 | types, 153 | groups, 154 | users, 155 | getGroupUsers, 156 | TYPE_G, 157 | TYPE_U, 158 | changeGId, 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/views/address_book/rule.vue: -------------------------------------------------------------------------------- 1 | 106 | 107 | 157 | 158 | 161 | -------------------------------------------------------------------------------- /src/views/audit/connList.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 84 | 85 | 88 | -------------------------------------------------------------------------------- /src/views/audit/reponsitories.js: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue' 2 | import { list, remove, fileList, fileRemove, batchDelete, fileBatchDelete } from '@/api/audit' 3 | import { ElMessage, ElMessageBox } from 'element-plus' 4 | import { useRoute } from 'vue-router' 5 | import { formatTime } from '@/utils/time' 6 | import { T } from '@/utils/i18n' 7 | 8 | export function useRepositories () { 9 | const listRes = reactive({ 10 | list: [], total: 0, loading: false, 11 | }) 12 | const listQuery = reactive({ 13 | page: 1, 14 | page_size: 10, 15 | peer_id: null, 16 | from_peer: null, 17 | }) 18 | 19 | const getList = async () => { 20 | listRes.loading = true 21 | const res = await list(listQuery).catch(_ => false) 22 | listRes.loading = false 23 | if (res) { 24 | listRes.list = res.data.list.map(item => { 25 | item.close_time = item.close_time ? formatTime(item.close_time * 1000) : '-' 26 | return item 27 | }) 28 | listRes.total = res.data.total 29 | } 30 | } 31 | const handlerQuery = () => { 32 | if (listQuery.page === 1) { 33 | getList() 34 | } else { 35 | listQuery.page = 1 36 | } 37 | } 38 | 39 | const del = async (row) => { 40 | const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('Delete') }), { 41 | confirmButtonText: T('Confirm'), 42 | cancelButtonText: T('Cancel'), 43 | type: 'warning', 44 | }).catch(_ => false) 45 | if (!cf) { 46 | return false 47 | } 48 | 49 | const res = await remove({ id: row.id }).catch(_ => false) 50 | if (res) { 51 | ElMessage.success(T('OperationSuccess')) 52 | getList() 53 | } 54 | } 55 | const batchdel = async (rows) => { 56 | const ids = rows.map(r => r.id) 57 | if (!ids.length) { 58 | ElMessage.warning(T('PleaseSelectData')) 59 | return false 60 | } 61 | const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('BatchDelete') }), { 62 | confirmButtonText: T('Confirm'), 63 | cancelButtonText: T('Cancel'), 64 | type: 'warning', 65 | }).catch(_ => false) 66 | if (!cf) { 67 | return false 68 | } 69 | 70 | const res = await batchDelete({ ids }).catch(_ => false) 71 | if (res) { 72 | ElMessage.success(T('OperationSuccess')) 73 | getList() 74 | } 75 | } 76 | return { 77 | listRes, 78 | listQuery, 79 | getList, 80 | handlerQuery, 81 | del, 82 | batchdel, 83 | } 84 | } 85 | 86 | export function useFileRepositories () { 87 | const listRes = reactive({ 88 | list: [], total: 0, loading: false, 89 | }) 90 | const listQuery = reactive({ 91 | page: 1, 92 | page_size: 10, 93 | peer_id: null, 94 | from_peer: null, 95 | }) 96 | 97 | const getList = async () => { 98 | listRes.loading = true 99 | const res = await fileList(listQuery).catch(_ => false) 100 | listRes.loading = false 101 | if (res) { 102 | listRes.list = res.data.list.map(item => { 103 | item.info = item.info ? JSON.parse(item.info) : '-' 104 | return item 105 | }) 106 | listRes.total = res.data.total 107 | } 108 | } 109 | const handlerQuery = () => { 110 | if (listQuery.page === 1) { 111 | getList() 112 | } else { 113 | listQuery.page = 1 114 | } 115 | } 116 | 117 | const del = async (row) => { 118 | const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('Delete') }), { 119 | confirmButtonText: T('Confirm'), 120 | cancelButtonText: T('Cancel'), 121 | type: 'warning', 122 | }).catch(_ => false) 123 | if (!cf) { 124 | return false 125 | } 126 | 127 | const res = await fileRemove({ id: row.id }).catch(_ => false) 128 | if (res) { 129 | ElMessage.success(T('OperationSuccess')) 130 | getList() 131 | } 132 | } 133 | const batchdel = async (rows) => { 134 | const ids = rows.map(r => r.id) 135 | if (!ids.length) { 136 | ElMessage.warning(T('PleaseSelectData')) 137 | return false 138 | } 139 | const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('BatchDelete') }), { 140 | confirmButtonText: T('Confirm'), 141 | cancelButtonText: T('Cancel'), 142 | type: 'warning', 143 | }).catch(_ => false) 144 | if (!cf) { 145 | return false 146 | } 147 | 148 | const res = await fileBatchDelete({ ids }).catch(_ => false) 149 | if (res) { 150 | ElMessage.success(T('OperationSuccess')) 151 | getList() 152 | } 153 | } 154 | return { 155 | listRes, 156 | listQuery, 157 | getList, 158 | handlerQuery, 159 | del, 160 | batchdel, 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/views/error-page/404.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /src/views/group/deviceGroupList.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 135 | 136 | 139 | -------------------------------------------------------------------------------- /src/views/group/index.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 153 | 154 | 157 | -------------------------------------------------------------------------------- /src/views/index/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 24 | 25 | 76 | -------------------------------------------------------------------------------- /src/views/login/log.js: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from 'vue' 2 | import { list as admin_fetchPeers } from '@/api/peer' 3 | import { list as my_fetchPeers } from '@/api/my/peer' 4 | import { ElMessage, ElMessageBox } from 'element-plus' 5 | import { useRoute } from 'vue-router' 6 | import { T } from '@/utils/i18n' 7 | import { batchDelete as admin_batchDelete, list as admin_list, remove as admin_remove } from '@/api/login_log' 8 | import { batchDelete as my_batchDelete, list as my_list, remove as my_remove } from '@/api/my/login_log' 9 | 10 | const apis = { 11 | admin: { batchDelete: admin_batchDelete, list: admin_list, remove: admin_remove, fetchPeers: admin_fetchPeers }, 12 | my: { batchDelete: my_batchDelete, list: my_list, remove: my_remove, fetchPeers: my_fetchPeers }, 13 | } 14 | 15 | export function useRepositories (api_type = 'my') { 16 | 17 | const listRes = reactive({ 18 | list: [], total: 0, loading: false, 19 | }) 20 | const listQuery = reactive({ 21 | page: 1, 22 | page_size: 10, 23 | is_my: 0, 24 | user_id: null, 25 | }) 26 | 27 | const getList = async () => { 28 | listRes.loading = true 29 | const res = await apis[api_type].list(listQuery).catch(_ => false) 30 | listRes.loading = false 31 | if (res) { 32 | //通过uuid补全peer信息 33 | const uuids = res.data.list.filter(item => item.uuid&&item.client==='client'&&!item.device_id).map(item => item.uuid) 34 | if(uuids.length > 0){ 35 | //uuids去重 36 | const uniqueUuids = [...new Set(uuids)] 37 | const peers = await apis[api_type].fetchPeers({ uuids: uniqueUuids }).catch(_ => false) 38 | if (peers?.data?.list) { 39 | res.data.list.forEach(item => { 40 | if (item.uuid) { 41 | item.peer = peers.data.list.find(peer => peer.uuid === item.uuid) 42 | } 43 | }) 44 | } 45 | } 46 | 47 | listRes.list = res.data.list 48 | listRes.total = res.data.total 49 | } 50 | } 51 | const handlerQuery = () => { 52 | if (listQuery.page === 1) { 53 | getList() 54 | } else { 55 | listQuery.page = 1 56 | } 57 | } 58 | 59 | const del = async (row) => { 60 | const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('Delete') }), { 61 | confirmButtonText: T('Confirm'), 62 | cancelButtonText: T('Cancel'), 63 | type: 'warning', 64 | }).catch(_ => false) 65 | if (!cf) { 66 | return false 67 | } 68 | 69 | const res = await apis[api_type].remove({ id: row.id }).catch(_ => false) 70 | if (res) { 71 | ElMessage.success(T('OperationSuccess')) 72 | getList() 73 | } 74 | } 75 | 76 | const batchdel = async (rows) => { 77 | const ids = rows.map(r => r.id) 78 | if (!ids.length) { 79 | ElMessage.warning(T('PleaseSelectData')) 80 | return false 81 | } 82 | const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('BatchDelete') }), { 83 | confirmButtonText: T('Confirm'), 84 | cancelButtonText: T('Cancel'), 85 | type: 'warning', 86 | }).catch(_ => false) 87 | if (!cf) { 88 | return false 89 | } 90 | 91 | const res = await apis[api_type].batchDelete({ ids }).catch(_ => false) 92 | if (res) { 93 | ElMessage.success(T('OperationSuccess')) 94 | getList() 95 | } 96 | } 97 | 98 | return { 99 | listRes, 100 | listQuery, 101 | getList, 102 | handlerQuery, 103 | del, 104 | batchdel, 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/views/login/log.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 95 | 96 | 103 | -------------------------------------------------------------------------------- /src/views/my/address_book/collection.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 99 | 100 | 103 | -------------------------------------------------------------------------------- /src/views/my/info.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 92 | 93 | 100 | -------------------------------------------------------------------------------- /src/views/my/login_log/index.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 75 | 76 | 83 | -------------------------------------------------------------------------------- /src/views/my/share_record/index.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 69 | 70 | 77 | -------------------------------------------------------------------------------- /src/views/my/tag/index.vue: -------------------------------------------------------------------------------- 1 | 84 | 85 | 122 | 123 | 151 | -------------------------------------------------------------------------------- /src/views/oauth/bind.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 69 | 70 | 103 | -------------------------------------------------------------------------------- /src/views/oauth/login.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 63 | 64 | 97 | -------------------------------------------------------------------------------- /src/views/peer/createABForm.vue: -------------------------------------------------------------------------------- 1 | 59 | 126 | 127 | 130 | -------------------------------------------------------------------------------- /src/views/register/index.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 91 | 92 | 158 | -------------------------------------------------------------------------------- /src/views/rustdesk/always_use_relay.vue: -------------------------------------------------------------------------------- 1 | 19 | 65 | 66 | 67 | 70 | -------------------------------------------------------------------------------- /src/views/rustdesk/blacklist.vue: -------------------------------------------------------------------------------- 1 | 33 | 91 | 94 | -------------------------------------------------------------------------------- /src/views/rustdesk/blocklist.vue: -------------------------------------------------------------------------------- 1 | 33 | 91 | 94 | -------------------------------------------------------------------------------- /src/views/rustdesk/must_login.vue: -------------------------------------------------------------------------------- 1 | 19 | 63 | 64 | 65 | 68 | -------------------------------------------------------------------------------- /src/views/rustdesk/options.js: -------------------------------------------------------------------------------- 1 | 2 | export const ID_TARGET = '21115' 3 | 4 | export const RELAY_TARGET = '21117' 5 | -------------------------------------------------------------------------------- /src/views/rustdesk/relay_servers.vue: -------------------------------------------------------------------------------- 1 | 19 | 63 | 66 | -------------------------------------------------------------------------------- /src/views/rustdesk/usage.vue: -------------------------------------------------------------------------------- 1 | 25 | 58 | 63 | -------------------------------------------------------------------------------- /src/views/share_record/index.js: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from 'vue' 2 | import { batchDelete as admin_batchDelete, list as admin_list, remove as admin_remove } from '@/api/share_record' 3 | import { batchDelete as my_batchDelete, list as my_list, remove as my_remove } from '@/api/my/share_record' 4 | import { ElMessage, ElMessageBox } from 'element-plus' 5 | import { T } from '@/utils/i18n' 6 | 7 | const apis = { 8 | admin: { batchDelete: admin_batchDelete, list: admin_list, remove: admin_remove }, 9 | my: { batchDelete: my_batchDelete, list: my_list, remove: my_remove }, 10 | } 11 | 12 | export function useRepositories (api_type = 'my') { 13 | const listRes = reactive({ 14 | list: [], total: 0, loading: false, 15 | }) 16 | const listQuery = reactive({ 17 | page: 1, 18 | page_size: 10, 19 | }) 20 | 21 | const getList = async () => { 22 | listRes.loading = true 23 | const res = await apis[api_type].list(listQuery).catch(_ => false) 24 | listRes.loading = false 25 | if (res) { 26 | listRes.list = res.data.list 27 | listRes.total = res.data.total 28 | } 29 | } 30 | const handlerQuery = () => { 31 | if (listQuery.page === 1) { 32 | getList() 33 | } else { 34 | listQuery.page = 1 35 | } 36 | } 37 | 38 | const del = async (row) => { 39 | const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('Delete') }), { 40 | confirmButtonText: T('Confirm'), 41 | cancelButtonText: T('Cancel'), 42 | type: 'warning', 43 | }).catch(_ => false) 44 | if (!cf) { 45 | return false 46 | } 47 | const res = await apis[api_type].remove({ id: row.id }).catch(_ => false) 48 | if (res) { 49 | ElMessage.success(T('OperationSuccess')) 50 | getList() 51 | } 52 | } 53 | 54 | const multipleSelection = ref([]) 55 | const toBatchDelete = async () => { 56 | if (multipleSelection.value.length === 0) { 57 | return 58 | } 59 | 60 | const ids = multipleSelection.value.map(r => r.id) 61 | if (!ids.length) { 62 | ElMessage.warning(T('PleaseSelectData')) 63 | return false 64 | } 65 | const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('BatchDelete') }), { 66 | confirmButtonText: T('Confirm'), 67 | cancelButtonText: T('Cancel'), 68 | type: 'warning', 69 | }).catch(_ => false) 70 | if (!cf) { 71 | return false 72 | } 73 | 74 | const res = await apis[api_type].batchDelete({ ids }).catch(_ => false) 75 | if (res) { 76 | ElMessage.success(T('OperationSuccess')) 77 | getList() 78 | } 79 | } 80 | 81 | const expired = (row) => { 82 | if (row.expire === 0) { 83 | return false 84 | } 85 | const now = new Date().getTime() 86 | const created_at = new Date(row.created_at).getTime() 87 | return row.expire * 1000 + created_at < now 88 | } 89 | 90 | return { 91 | listRes, 92 | listQuery, 93 | getList, 94 | handlerQuery, 95 | del, 96 | multipleSelection, 97 | toBatchDelete, 98 | expired, 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/views/share_record/index.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 90 | 91 | 98 | -------------------------------------------------------------------------------- /src/views/user/composables/edit.js: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, reactive, watch } from 'vue' 2 | import { create, detail, update, remove } from '@/api/user' 3 | import { ElMessage, ElMessageBox } from 'element-plus' 4 | import { useRouter } from 'vue-router' 5 | import { list as groups } from '@/api/group' 6 | import { T } from '@/utils/i18n' 7 | 8 | export function useGetDetail (id) { 9 | let item = ref({}) //保留原始值 10 | let form = ref({}) 11 | const groupsList = ref([]) 12 | const getDetail = async (id) => { 13 | const res = await detail(id) 14 | item.value = { ...res.data } 15 | form.value = { ...res.data } 16 | } 17 | if (id > 0) { 18 | onMounted(_ => {getDetail(id)}) 19 | } 20 | 21 | const getGroups = async () => { 22 | const res = await groups({ page_size: 9999 }).catch(_ => false) 23 | if (res) { 24 | groupsList.value = res.data.list 25 | } 26 | } 27 | onMounted(getGroups) 28 | return { 29 | form, 30 | item, 31 | getDetail, 32 | groupsList, 33 | } 34 | } 35 | 36 | export function useSubmit (form, id) { 37 | const root = ref(null) 38 | const router = useRouter() 39 | const rules = reactive({ 40 | username: [{ required: true, message: T('ParamRequired', { param: T('Username') }) }], 41 | // email: [{ required: true, message: T('ParamRequired', { param: T('Email') }) }], 42 | group_id: [{ required: true, message: T('ParamRequired', { param: T('Group') }) }], 43 | // nickname: [{ required: true, message: '昵称是必须的' }], 44 | status: [{ required: true, message: T('ParamRequired', { param: T('Status') }) }], 45 | }) 46 | 47 | const validate = async () => { 48 | const res = await root.value.validate().catch(err => false) 49 | return res 50 | } 51 | 52 | const submitCreate = async () => { 53 | const res = await create(form.value).catch(_ => false) 54 | return res.code === 0 55 | } 56 | 57 | const submitUpdate = async () => { 58 | const res = await update(form.value).catch(_ => false) 59 | return res.code === 0 60 | } 61 | const submitFunc = id > 0 ? submitUpdate : submitCreate 62 | 63 | const submit = async () => { 64 | const v = await validate() 65 | if (!v) { 66 | return 67 | } 68 | 69 | const res = await submitFunc() 70 | if (res) { 71 | ElMessage.success(T('OperationSuccess')) 72 | router.back() 73 | } 74 | } 75 | 76 | const cancel = () => { 77 | router.back() 78 | } 79 | 80 | return { 81 | root, 82 | rules, 83 | validate, 84 | submit, 85 | cancel, 86 | } 87 | } 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/views/user/composables/index.js: -------------------------------------------------------------------------------- 1 | import { onMounted, reactive, watch } from 'vue' 2 | import { list, remove, changePwd } from '@/api/user' 3 | import { list as groups } from '@/api/group' 4 | import { useRouter } from 'vue-router' 5 | import { ElMessageBox, ElMessage } from 'element-plus' 6 | import { T } from '@/utils/i18n' 7 | 8 | export function useRepositories () { 9 | 10 | const listRes = reactive({ 11 | list: [], total: 0, loading: false, 12 | groups: [], 13 | }) 14 | const listQuery = reactive({ 15 | page: 1, 16 | page_size: 10, 17 | username: '', 18 | }) 19 | 20 | const getList = async () => { 21 | listRes.loading = true 22 | const res = await list(listQuery).catch(_ => false) 23 | listRes.loading = false 24 | if (res) { 25 | listRes.list = res.data.list 26 | listRes.total = res.data.total 27 | } 28 | } 29 | 30 | const handlerQuery = () => { 31 | if (listQuery.page === 1) { 32 | getList() 33 | } else { 34 | listQuery.page = 1 35 | //由watch 触发 36 | } 37 | } 38 | 39 | const getGroups = async () => { 40 | const res = await groups({ page_size: 9999 }).catch(_ => false) 41 | if (res) { 42 | listRes.groups = res.data.list 43 | } 44 | } 45 | onMounted(getGroups) 46 | 47 | onMounted(getList) 48 | 49 | watch(() => listQuery.page, getList) 50 | watch(() => listQuery.page_size, handlerQuery) 51 | return { 52 | listRes, 53 | listQuery, 54 | handlerQuery, 55 | getList, 56 | getGroups, 57 | } 58 | } 59 | 60 | export function useToEditOrAdd () { 61 | const router = useRouter() 62 | const toEdit = (row) => { 63 | router.push('/user/edit/' + row.id) 64 | } 65 | const toAdd = () => { 66 | router.push('/user/add') 67 | } 68 | const toTag = (row) => { 69 | router.push('/user/tag/?user_id=' + row.id) 70 | } 71 | const toAddressBook = (row) => { 72 | router.push('/user/addressBook/?user_id=' + row.id) 73 | } 74 | return { 75 | toAdd, 76 | toEdit, 77 | toTag, 78 | toAddressBook, 79 | } 80 | } 81 | 82 | export function useDel () { 83 | const del = async (id) => { 84 | const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('Delete') }), { 85 | confirmButtonText: T('Confirm'), 86 | cancelButtonText: T('Cancel'), 87 | type: 'warning', 88 | }).catch(_ => false) 89 | if (!cf) { 90 | return false 91 | } 92 | 93 | const res = remove({ id }).catch(_ => false) 94 | if (res) { 95 | ElMessage.success(T('OperationSuccess')) 96 | } 97 | return res 98 | } 99 | return { 100 | del, 101 | } 102 | } 103 | 104 | export function useChangePwd () { 105 | const changePass = async (admin) => { 106 | const input = await ElMessageBox.prompt(T('PleaseInputNewPassword'), T('ResetPassword'), { 107 | confirmButtonText: T('Confirm'), 108 | cancelButtonText: T('Cancel'), 109 | }).catch(_ => false) 110 | if (!input) { 111 | return 112 | } 113 | const confirm = await ElMessageBox.confirm(T('Confirm?', { param: T('ResetPassword') }), { 114 | confirmButtonText: T('Confirm'), 115 | cancelButtonText: T('Cancel'), 116 | }).catch(_ => false) 117 | if (!confirm) { 118 | return 119 | } 120 | const res = await changePwd({ id: admin.id, password: input.value }).catch(_ => false) 121 | if (!res) { 122 | return 123 | } 124 | ElMessage.success(T('OperationSuccess')) 125 | } 126 | 127 | return { changePass } 128 | } 129 | -------------------------------------------------------------------------------- /src/views/user/edit.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 55 | 56 | 60 | -------------------------------------------------------------------------------- /src/views/user/index.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 103 | 104 | 106 | -------------------------------------------------------------------------------- /src/views/user/token.js: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue' 2 | import { batchRemove, list, remove } from '@/api/user_token' 3 | import { ElMessage, ElMessageBox } from 'element-plus' 4 | import { useRoute } from 'vue-router' 5 | import { T } from '@/utils/i18n' 6 | 7 | export function useRepositories () { 8 | const route = useRoute() 9 | const user_id = route.query?.user_id 10 | 11 | const listRes = reactive({ 12 | list: [], total: 0, loading: false, 13 | }) 14 | const listQuery = reactive({ 15 | page: 1, 16 | page_size: 10, 17 | is_my: 0, 18 | user_id: user_id ? parseInt(user_id) : null, 19 | }) 20 | 21 | const getList = async () => { 22 | listRes.loading = true 23 | const res = await list(listQuery).catch(_ => false) 24 | listRes.loading = false 25 | if (res) { 26 | listRes.list = res.data.list 27 | listRes.total = res.data.total 28 | } 29 | } 30 | const handlerQuery = () => { 31 | if (listQuery.page === 1) { 32 | getList() 33 | } else { 34 | listQuery.page = 1 35 | } 36 | } 37 | 38 | const del = async (row) => { 39 | const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('Logout') }), { 40 | confirmButtonText: T('Confirm'), 41 | cancelButtonText: T('Cancel'), 42 | type: 'warning', 43 | }).catch(_ => false) 44 | if (!cf) { 45 | return false 46 | } 47 | 48 | const res = await remove({ id: row.id }).catch(_ => false) 49 | if (res) { 50 | ElMessage.success(T('OperationSuccess')) 51 | getList() 52 | } 53 | } 54 | 55 | const batchDelete = async (ids) => { 56 | const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('BatchDelete') }), { 57 | confirmButtonText: T('Confirm'), 58 | cancelButtonText: T('Cancel'), 59 | type: 'warning', 60 | }).catch(_ => false) 61 | if (!cf) { 62 | return false 63 | } 64 | 65 | const res = await batchRemove({ ids }).catch(_ => false) 66 | if (res) { 67 | ElMessage.success(T('OperationSuccess')) 68 | getList() 69 | } 70 | } 71 | 72 | return { 73 | listRes, 74 | listQuery, 75 | getList, 76 | handlerQuery, 77 | del, 78 | batchDelete, 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/views/user/token.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 103 | 104 | 111 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import * as path from 'path' 3 | import * as dotenv from 'dotenv' 4 | import * as fs from 'fs' 5 | import vue from '@vitejs/plugin-vue' 6 | 7 | const NODE_ENV = process.env.NODE_ENV || 'development' 8 | const envFile = `.env.${NODE_ENV}` 9 | const envConfig = dotenv.parse(fs.readFileSync(envFile)) 10 | for (const k in envConfig) { 11 | process.env[k] = envConfig[k] 12 | } 13 | 14 | let alias = { 15 | '@': path.resolve(__dirname, './src'), 16 | 'vue$': 'vue/dist/vue.runtime.esm-bundler.js', 17 | } 18 | 19 | const conf = { 20 | base: './', // index.html文件所在位置 21 | root: './', // js导入的资源路径,src 22 | server: { 23 | open: true, 24 | port: process.env.VITE_DEV_PORT, 25 | proxy: { 26 | [process.env.VITE_SERVER_API]: { 27 | target: process.env.VITE_SERVER_PATH, 28 | // rewrite: path => path.replace(/^\/api/, '/api'), //为了模拟 29 | changeOrigin: true, 30 | }, 31 | }, 32 | }, 33 | build: { 34 | target: 'es2020', 35 | minify: 'esbuild', // 是否进行压缩,boolean | 'terser' | 'esbuild',默认使用 esbuild 36 | manifest: false, // 是否产出maifest.json 37 | sourcemap: false, // 是否产出soucemap.json 38 | emptyOutDir: true, 39 | outDir: 'dist', // 产出目录 40 | rollupOptions: { 41 | output: { 42 | manualChunks (id) { 43 | if (id.includes('node_modules')) { 44 | const arr = id.toString().split('node_modules/')[1].split('/') 45 | switch (arr[0]) { 46 | case '@popperjs': 47 | case '@vue': 48 | case 'axios': 49 | case 'element-plus': 50 | case '@element-plus': 51 | return '_' + arr[0] 52 | default : 53 | return '__vendor' 54 | } 55 | }else if(id.includes('Gwen-admin/src')){ 56 | //src 下的都打包到一起 不然很多小文件 57 | return 'gwen' 58 | } 59 | }, 60 | chunkFileNames: 'static/chunk/[name]-[hash].js', 61 | entryFileNames: 'static/entry/[name]-[hash].js', 62 | assetFileNames: 'static/[ext]/[name]-[hash].[ext]' 63 | }, 64 | }, 65 | }, 66 | css: { 67 | preprocessorOptions: { 68 | scss: { 69 | javascriptEnabled: true, 70 | }, 71 | }, 72 | }, 73 | resolve: { 74 | alias, 75 | }, 76 | plugins: [ 77 | vue(), 78 | ], 79 | } 80 | 81 | // https://vitejs.dev/config/ 82 | export default defineConfig(conf) 83 | --------------------------------------------------------------------------------