Comparer les révisions
46 Révisions
| Auteur | SHA1 | Date | |
|---|---|---|---|
| 3998c2a921 | |||
| ffe0961b4a | |||
| c5d16aed80 | |||
| fe2626f6cb | |||
| 4be05adb0c | |||
| 09a40d3502 | |||
| 35bac6b91c | |||
| 15443701c6 | |||
| 5bbf0ca7d8 | |||
| 9b2af8794a | |||
| fc7e0595a1 | |||
| 42cc3974d4 | |||
| 3084e40039 | |||
| 4b818f73e5 | |||
| 269bd0fd87 | |||
| 48a38156f7 | |||
| 357f34e0e4 | |||
| 0dfab9edfb | |||
| 0967e78ab7 | |||
| 83f25444ba | |||
| 7129dccbbc | |||
| 68bbcf17b0 | |||
| 24c480b2b7 | |||
| 00b09aae76 | |||
| f822e5bebf | |||
| cb7837e6e7 | |||
| b1ebedefc6 | |||
| c20592620d | |||
| d22aae110f | |||
| a631c7f8f0 | |||
| aeafcdde60 | |||
| 8617959d15 | |||
| 68ebc75e59 | |||
| 8d89478913 | |||
| cc8fa46299 | |||
| e4342ae3af | |||
| ce7ee9cce3 | |||
| 96cf2b29d5 | |||
| 0e9391a4c5 | |||
| 127419e89b | |||
| 7cf1a14950 | |||
| aab4ea4785 | |||
| bf00eb3fed | |||
| a1e5e9a9da | |||
| 21254e2b2c | |||
| f589d923e9 |
+2
-2
@@ -1,10 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Rustdesk-Api-Admin</title>
|
||||
<title>Rustdesk API Admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
Générée
+2083
-543
Fichier diff supprimé car celui-ci est trop grand
Voir la Diff
+11
-10
@@ -7,25 +7,26 @@
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "1.7.4",
|
||||
"axios": "1.8.2",
|
||||
"clipboard": "2.0.4",
|
||||
"element-plus": "^2.8.2",
|
||||
"fast-sha256": "^1.3.0",
|
||||
"js-cookie": "^3.0.1",
|
||||
"marked": "^15.0.4",
|
||||
"normalize.css": "^8.0.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "2.0.3",
|
||||
"vue": "3.2.37",
|
||||
"vue-router": "^4.0.12",
|
||||
"fast-sha256": "^1.3.0",
|
||||
"clipboard": "2.0.4"
|
||||
"pinia": "2.2.8",
|
||||
"vue": "3.5.13",
|
||||
"vue-router": "^4.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@element-plus/icons": "0.0.11",
|
||||
"@vitejs/plugin-vue": "^1.9.3",
|
||||
"@vitejs/plugin-vue": "5.2.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"qs": "^6.10.2",
|
||||
"sass-loader": "^12.3.0",
|
||||
"sass": "^1.43.4",
|
||||
"vite": "^2.9.18",
|
||||
"ts-proto": "^1.141.1"
|
||||
"sass-loader": "^12.3.0",
|
||||
"ts-proto": "^1.141.1",
|
||||
"vite": "6.3.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function list (params) {
|
||||
return request({
|
||||
url: '/device_group/list',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
export function detail (id) {
|
||||
return request({
|
||||
url: `/device_group/detail/${id}`,
|
||||
})
|
||||
}
|
||||
|
||||
export function create (data) {
|
||||
return request({
|
||||
url: '/device_group/create',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function update (data) {
|
||||
return request({
|
||||
url: '/device_group/update',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function remove (data) {
|
||||
return request({
|
||||
url: '/device_group/delete',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function list (params) {
|
||||
return request({
|
||||
url: '/my/login_log/list',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
export function remove (data) {
|
||||
return request({
|
||||
url: '/my/login_log/delete',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function batchDelete (data) {
|
||||
return request({
|
||||
url: '/my/login_log/batchDelete',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function list (params) {
|
||||
return request({
|
||||
url: '/rustdesk/cmdList',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
export function create (data) {
|
||||
return request({
|
||||
url: '/rustdesk/cmdCreate',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function update (data) {
|
||||
return request({
|
||||
url: '/rustdesk/cmdUpdate',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function remove (data) {
|
||||
return request({
|
||||
url: '/rustdesk/cmdDelete',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function sendCmd (data) {
|
||||
return request({
|
||||
url: '/rustdesk/sendCmd',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
@@ -52,14 +52,14 @@
|
||||
new_password: '',
|
||||
confirmPwd: '',
|
||||
})
|
||||
const chagePwdRules = reactive({
|
||||
old_password: [{ required: true, message: '请输入旧密码', trigger: 'blur' }],
|
||||
const chagePwdRules = computed(_ => ({
|
||||
old_password: [{ required: true, message: T('ParamRequired', { param: T('OldPassword') }), trigger: 'blur' }],
|
||||
new_password: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ required: true, message: T('ParamRequired', { param: T('NewPassword') }), trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === changePwdForm.old_password) {
|
||||
callback(new Error('新密码不能与旧密码相同'))
|
||||
callback(new Error(T('NewPasswordEqualOldPassword'))) //'新密码不能与旧密码相同'
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
@@ -67,11 +67,11 @@
|
||||
trigger: 'blur',
|
||||
}],
|
||||
confirmPwd: [
|
||||
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||||
{ required: true, message: T('ParamRequired', { param: T('ConfirmPassword') }), trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value !== changePwdForm.new_password) {
|
||||
callback(new Error('两次输入密码不一致'))
|
||||
callback(new Error(T('PasswordNotMatchConfirmPassword')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
@@ -79,7 +79,7 @@
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
})
|
||||
}))
|
||||
const cpwd = ref(null)
|
||||
const cancelChangePwd = () => {
|
||||
emit('update:visible', false)
|
||||
@@ -94,9 +94,9 @@
|
||||
return
|
||||
}
|
||||
console.log('changePassword')
|
||||
const confirm = await ElMessageBox.confirm('确定修改密码么?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
const confirm = await ElMessageBox.confirm(T('Confirm?', { param: T('ChangePassword') }), {
|
||||
confirmButtonText: T('Confirm'),
|
||||
cancelButtonText: T('Cancel'),
|
||||
}).catch(_ => false)
|
||||
if (!confirm) {
|
||||
return
|
||||
@@ -105,7 +105,7 @@
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
ElMessageBox.alert('修改成功', '修改密码', {
|
||||
ElMessageBox.alert(T('OperationSuccess'), T('ChangePassword'), {
|
||||
autofocus: true,
|
||||
confirmButtonText: 'OK',
|
||||
callback: (action) => {
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scroll-sidebar{
|
||||
position: fixed;
|
||||
}
|
||||
<style scoped lang="scss">
|
||||
.scroll-sidebar {
|
||||
background-color: #2d3a4b;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<el-main class="app-main">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition mode="out-in" name="el-fade-in-linear">
|
||||
<keep-alive :include="[...cachedTags]">
|
||||
<keep-alive :include="cachedTags">
|
||||
<component :is="Component"/>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
@@ -60,7 +60,6 @@
|
||||
}
|
||||
|
||||
.app-left {
|
||||
height: 100%;
|
||||
transition: width 0.5s;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ NProgress.configure({ showSpinner: false }) // NProgress Configuration
|
||||
const whiteList = ['/login', '/register']
|
||||
const routeStore = useRouteStore(pinia)
|
||||
const appStore = useAppStore(pinia)
|
||||
appStore.getAdminConfig()
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
|
||||
document.title = T(to.meta?.title) + ' - ' + appStore.setting.title
|
||||
@@ -40,7 +41,9 @@ router.beforeEach(async (to, from, next) => {
|
||||
} else {
|
||||
next({ ...to, replace: true })
|
||||
}
|
||||
} else {
|
||||
}/* else if (to.path === '/404') {
|
||||
next({path: to.redirectedFrom?.fullPath, replace: true})
|
||||
}*/ else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export const asyncRoutes = [
|
||||
{
|
||||
path: '/my',
|
||||
name: 'My',
|
||||
redirect: '/my/info',
|
||||
redirect: '/',
|
||||
meta: { title: 'My', icon: 'UserFilled' },
|
||||
component: () => import('@/layout/index.vue'),
|
||||
children: [
|
||||
@@ -91,6 +91,12 @@ export const asyncRoutes = [
|
||||
meta: { title: 'ShareRecord', icon: 'Share' /*keepAlive: true*/ },
|
||||
component: () => import('@/views/my/share_record/index.vue'),
|
||||
},
|
||||
{
|
||||
path: 'loginLog',
|
||||
name: 'MyLoginLog',
|
||||
meta: { title: 'LoginLog', icon: 'List' /*keepAlive: true*/ },
|
||||
component: () => import('@/views/my/login_log/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -112,6 +118,12 @@ export const asyncRoutes = [
|
||||
meta: { title: 'GroupManage', icon: 'ChatRound' /*keepAlive: true*/ },
|
||||
component: () => import('@/views/group/index.vue'),
|
||||
},
|
||||
{
|
||||
path: 'deviceGroup',
|
||||
name: 'DeviceGroup',
|
||||
meta: { title: 'DeviceGroupManage', icon: 'ChatRound' /*keepAlive: true*/ },
|
||||
component: () => import('@/views/group/deviceGroupList.vue'),
|
||||
},
|
||||
{
|
||||
path: 'index',
|
||||
name: 'UserList',
|
||||
@@ -184,6 +196,12 @@ export const asyncRoutes = [
|
||||
meta: { title: 'ShareRecord', icon: 'Share' /*keepAlive: true*/ },
|
||||
component: () => import('@/views/share_record/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/serverCmd',
|
||||
name: 'ServerCmd',
|
||||
meta: { title: 'ServerCmd', icon: 'Tools' /*keepAlive: true*/ },
|
||||
component: () => import('@/views/rustdesk/control.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
+32
-7
@@ -6,22 +6,24 @@ import ko from 'element-plus/es/locale/lang/ko'
|
||||
import ru from 'element-plus/es/locale/lang/ru'
|
||||
import fr from 'element-plus/es/locale/lang/fr'
|
||||
import es from 'element-plus/es/locale/lang/es'
|
||||
import { admin, app } from '@/api/config'
|
||||
import zhTw from 'element-plus/es/locale/lang/zh-tw'
|
||||
import { admin, app, server } from '@/api/config'
|
||||
|
||||
const langs = {
|
||||
'zh-CN': { name: '中文', value: zhCn, sideBarWidth: '210px' },
|
||||
'en': { name: 'English', value: en, sideBarWidth: '230px' },
|
||||
'fr': { name: 'Français', value: fr, sideBarWidth: '280px' },
|
||||
'ko': { name: '한국어', value: ko, sideBarWidth: '230px' },
|
||||
'ru': { name: 'русский', value: ru, sideBarWidth: '250px' },
|
||||
'es': { name: 'español', value: es, sideBarWidth: '280px' },
|
||||
'ru': { name: 'Русский', value: ru, sideBarWidth: '250px' },
|
||||
'es': { name: 'Español', value: es, sideBarWidth: '280px' },
|
||||
'zh-TW': { name: '中文繁体', value: zhTw, sideBarWidth: '210px' },
|
||||
}
|
||||
const defaultLang = localStorage.getItem('lang') || navigator.language || 'zh-CN'
|
||||
export const useAppStore = defineStore({
|
||||
id: 'App',
|
||||
state: () => ({
|
||||
setting: {
|
||||
title: 'Rustdesk-Api-Admin',
|
||||
title: 'Rustdesk API Admin',
|
||||
hello: '',
|
||||
sideIsCollapse: false,
|
||||
logo,
|
||||
@@ -31,6 +33,12 @@ export const useAppStore = defineStore({
|
||||
appConfig: {
|
||||
web_client: 1,
|
||||
},
|
||||
rustdeskConfig: {
|
||||
'id_server': '',
|
||||
'key': '',
|
||||
'relay_server': '',
|
||||
'api_server': '',
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -50,20 +58,37 @@ export const useAppStore = defineStore({
|
||||
loadConfig () {
|
||||
this.getAppConfig()
|
||||
this.getAdminConfig()
|
||||
this.loadRustdeskConfig()
|
||||
},
|
||||
getAppConfig () {
|
||||
console.log('getAppConfig')
|
||||
app().then(res => {
|
||||
return app().then(res => {
|
||||
this.setting.appConfig = res.data
|
||||
})
|
||||
},
|
||||
getAdminConfig () {
|
||||
console.log('getAdminConfig')
|
||||
admin().then(res => {
|
||||
this.setting.title = res.data.title
|
||||
return admin().then(res => {
|
||||
this.replaceAdminTitle(res.data.title)
|
||||
this.setting.hello = res.data.hello
|
||||
})
|
||||
},
|
||||
replaceAdminTitle (newTitle) {
|
||||
document.title = document.title.replace(`- ${this.setting.title}`, `- ${newTitle}`)
|
||||
this.setting.title = newTitle
|
||||
},
|
||||
async loadRustdeskConfig () {
|
||||
console.log('loadRustdeskConfig')
|
||||
const res = await server().catch(_ => false)
|
||||
if (res) {
|
||||
this.setting.rustdeskConfig = res.data
|
||||
const prefix = 'wc-'
|
||||
localStorage.setItem(`${prefix}custom-rendezvous-server`, res.data.id_server)
|
||||
localStorage.setItem(`${prefix}key`, res.data.key)
|
||||
localStorage.setItem(`${prefix}api-server`, res.data.api_server)
|
||||
}
|
||||
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@ $primaryColor: #409eff;
|
||||
--tag-bg-color: #efefef;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
||||
"Helvetica Neue", Arial, "Noto Sans", sans-serif;
|
||||
}
|
||||
|
||||
.list-body {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function jsonToCsv (data) {
|
||||
let keys = Object.keys(data[0])
|
||||
csv += keys.join(',') + '\n'
|
||||
data.forEach(row => {
|
||||
csv += keys.map(key => `"${row[key]}"`).join(',') + '\n'
|
||||
csv += keys.map(key => `"${row[key].toString().replaceAll('"', '""')}"`).join(',') + '\n'
|
||||
})
|
||||
return new Blob([csv], { type: 'text/csv' })
|
||||
}
|
||||
|
||||
@@ -4,19 +4,21 @@ import zhCN from '@/utils/i18n/zh_CN.json'
|
||||
import ko from '@/utils/i18n/ko.json'
|
||||
import ru from '@/utils/i18n/ru.json'
|
||||
import es from '@/utils/i18n/es.json'
|
||||
import zhTW from '@/utils/i18n/zh_TW.json'
|
||||
import { useAppStore } from '@/store/app'
|
||||
|
||||
export function T (key, params, num = 0) {
|
||||
const appStore = useAppStore()
|
||||
const lang = appStore.setting.lang
|
||||
const trans = {
|
||||
const trans = {
|
||||
'en': en,
|
||||
'fr': fr,
|
||||
'zh-CN': zhCN,
|
||||
'ko': ko,
|
||||
'ru': ru,
|
||||
'es': es,
|
||||
}
|
||||
'zh-TW': zhTW,
|
||||
}
|
||||
export function T (key, params, num = 0) {
|
||||
const appStore = useAppStore()
|
||||
const lang = appStore.setting.lang
|
||||
const tran = trans[lang]?.[key]
|
||||
if (!tran) {
|
||||
return key
|
||||
|
||||
@@ -439,6 +439,15 @@
|
||||
"LastOnlineIp": {
|
||||
"One": "Last Online Ip"
|
||||
},
|
||||
"OldPassword": {
|
||||
"One": "Old Password"
|
||||
},
|
||||
"NewPasswordEqualOldPassword": {
|
||||
"One": "New Password cannot be the same as Old Password"
|
||||
},
|
||||
"NewPassword": {
|
||||
"One": "New Password"
|
||||
},
|
||||
"ConfirmPassword": {
|
||||
"One": "Confirm Password"
|
||||
},
|
||||
@@ -468,5 +477,59 @@
|
||||
},
|
||||
"Captcha": {
|
||||
"One": "Captcha"
|
||||
},
|
||||
"ServerCmd": {
|
||||
"One": "Server Cmd"
|
||||
},
|
||||
"Simple": {
|
||||
"One": "Simple"
|
||||
},
|
||||
"Advanced": {
|
||||
"One": "Advanced"
|
||||
},
|
||||
"Available": {
|
||||
"One": "Available"
|
||||
},
|
||||
"NotAvailable": {
|
||||
"One": "Not Available"
|
||||
},
|
||||
"ServerCmdTips": {
|
||||
"One": "This feature is experimental. Please ensure that the API and hbbs and hbbr are on the same loopback address. For more information, please refer to the {wiki}"
|
||||
},
|
||||
"Refresh": {
|
||||
"One": "Refresh"
|
||||
},
|
||||
"Save": {
|
||||
"One": "Save"
|
||||
},
|
||||
"SendCustom": {
|
||||
"One": "Send Custom"
|
||||
},
|
||||
"Send": {
|
||||
"One": "Send"
|
||||
},
|
||||
"SendCmd": {
|
||||
"One": "Send Cmd"
|
||||
},
|
||||
"Result": {
|
||||
"One": "Result"
|
||||
},
|
||||
"DeviceGroupManage": {
|
||||
"One": "Device Group Manage"
|
||||
},
|
||||
"Drop file here or click to upload": {
|
||||
"One": "Drop file here or click to upload"
|
||||
},
|
||||
"Please upload csv file": {
|
||||
"One": "Please upload csv file"
|
||||
},
|
||||
"Columns": {
|
||||
"One": "Columns"
|
||||
},
|
||||
"You can reference export file": {
|
||||
"One": "You can reference export file"
|
||||
},
|
||||
"Remark": {
|
||||
"One": "Remark"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,6 +439,15 @@
|
||||
"LastOnlineIp": {
|
||||
"One": "Última IP en línea"
|
||||
},
|
||||
"OldPassword": {
|
||||
"One": "Contraseña antigua"
|
||||
},
|
||||
"NewPasswordEqualOldPassword": {
|
||||
"One": "La nueva contraseña no puede ser igual a la antigua"
|
||||
},
|
||||
"NewPassword": {
|
||||
"One": "Nueva contraseña"
|
||||
},
|
||||
"ConfirmPassword": {
|
||||
"One": "Confirmar contraseña"
|
||||
},
|
||||
@@ -471,5 +480,56 @@
|
||||
},
|
||||
"Captcha": {
|
||||
"One": "Captcha"
|
||||
},
|
||||
"ServerCmd": {
|
||||
"One": "Comando del servidor"
|
||||
},
|
||||
"Simple": {
|
||||
"One": "Simple"
|
||||
},
|
||||
"Advanced": {
|
||||
"One": "Avanzado"
|
||||
},
|
||||
"Available": {
|
||||
"One": "Disponible"
|
||||
},
|
||||
"NotAvailable": {
|
||||
"One": "No disponible"
|
||||
},
|
||||
"ServerCmdTips": {
|
||||
"One": "Esta función es experimental. Asegúrese de que la API y hbbs y hbbr estén en la misma dirección de bucle de retorno. Para obtener más información, consulte el {wiki}"
|
||||
},
|
||||
"Refresh": {
|
||||
"One": "Actualizar"
|
||||
},
|
||||
"Save": {
|
||||
"One": "Guardar"
|
||||
},
|
||||
"SendCustom": {
|
||||
"One": "Enviar personalizado"
|
||||
},
|
||||
"Send": {
|
||||
"One": "Enviar"
|
||||
},
|
||||
"SendCmd": {
|
||||
"One": "Enviar comando"
|
||||
},
|
||||
"Result": {
|
||||
"One": "Resultado"
|
||||
},
|
||||
"Drop file here or click to upload": {
|
||||
"One": "Suelte el archivo aquí o haga clic para cargar"
|
||||
},
|
||||
"Please upload csv file": {
|
||||
"One": "Por favor, suba un archivo csv"
|
||||
},
|
||||
"Columns": {
|
||||
"One": "Columnas"
|
||||
},
|
||||
"You can reference export file": {
|
||||
"One": "Puede hacer referencia al archivo de exportación"
|
||||
},
|
||||
"Remark": {
|
||||
"One": "Remark"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,43 +89,43 @@
|
||||
"Create": {
|
||||
"One": "Créer"
|
||||
},
|
||||
"Update" : {
|
||||
"One" : "Mise à jour"
|
||||
"Update": {
|
||||
"One": "Mise à jour"
|
||||
},
|
||||
"LoginName" : {
|
||||
"One" : "Nom de connexion"
|
||||
"LoginName": {
|
||||
"One": "Nom de connexion"
|
||||
},
|
||||
"Submit" : {
|
||||
"One" : "Envoyer"
|
||||
"Submit": {
|
||||
"One": "Envoyer"
|
||||
},
|
||||
"OperationSuccess" : {
|
||||
"One" : "Opération réussie"
|
||||
"OperationSuccess": {
|
||||
"One": "Opération réussie"
|
||||
},
|
||||
"Owner" : {
|
||||
"One" : "Propriétaire"
|
||||
"Owner": {
|
||||
"One": "Propriétaire"
|
||||
},
|
||||
"Name" : {
|
||||
"One" : "Nom"
|
||||
"Name": {
|
||||
"One": "Nom"
|
||||
},
|
||||
"Color" : {
|
||||
"One" : "Couleur"
|
||||
"Color": {
|
||||
"One": "Couleur"
|
||||
},
|
||||
"CreatedAt" : {
|
||||
"One" : "Créé le"
|
||||
"CreatedAt": {
|
||||
"One": "Créé le"
|
||||
},
|
||||
"UpdatedAt" : {
|
||||
"One" : "Mis à jour le"
|
||||
"UpdatedAt": {
|
||||
"One": "Mis à jour le"
|
||||
},
|
||||
"Memory" : {
|
||||
"One" : "Mémoire"
|
||||
"Memory": {
|
||||
"One": "Mémoire"
|
||||
},
|
||||
"Os" : {
|
||||
"One" : "Os"
|
||||
"Os": {
|
||||
"One": "Os"
|
||||
},
|
||||
"Uuid" : {
|
||||
"One" : "Uuid"
|
||||
"Uuid": {
|
||||
"One": "Uuid"
|
||||
},
|
||||
"Version" : {
|
||||
"Version": {
|
||||
"One": "Version"
|
||||
},
|
||||
"Type": {
|
||||
@@ -439,6 +439,15 @@
|
||||
"LastOnlineIp": {
|
||||
"One": "Dernière adresse IP en ligne"
|
||||
},
|
||||
"OldPassword": {
|
||||
"One": "Ancien mot de passe"
|
||||
},
|
||||
"NewPasswordEqualOldPassword": {
|
||||
"One": "Le nouveau mot de passe est identique à l'ancien mot de passe"
|
||||
},
|
||||
"NewPassword": {
|
||||
"One": "Nouveau mot de passe"
|
||||
},
|
||||
"ConfirmPassword": {
|
||||
"One": "Confirmer le mot de passe"
|
||||
},
|
||||
@@ -474,5 +483,56 @@
|
||||
},
|
||||
"Captcha": {
|
||||
"One": "Captcha"
|
||||
},
|
||||
"ServerCmd": {
|
||||
"One": "Commande serveur"
|
||||
},
|
||||
"Simple": {
|
||||
"One": "Simple"
|
||||
},
|
||||
"Advanced": {
|
||||
"One": "Avancé"
|
||||
},
|
||||
"Available": {
|
||||
"One": "Disponible"
|
||||
},
|
||||
"NotAvailable": {
|
||||
"One": "Non disponible"
|
||||
},
|
||||
"ServerCmdTips": {
|
||||
"One": "Cette fonctionnalité est expérimentale. Veuillez vous assurer que l'API et hbbs et hbbr sont sur la même adresse de boucle locale. Pour plus d'informations, veuillez consulter le {wiki}"
|
||||
},
|
||||
"Refresh": {
|
||||
"One": "Actualiser"
|
||||
},
|
||||
"Save": {
|
||||
"One": "Enregistrer"
|
||||
},
|
||||
"SendCustom": {
|
||||
"One": "Envoyer personnalisé"
|
||||
},
|
||||
"Send": {
|
||||
"One": "Envoyer"
|
||||
},
|
||||
"SendCmd": {
|
||||
"One": "Envoyer Cmd"
|
||||
},
|
||||
"Result": {
|
||||
"One": "Résultat"
|
||||
},
|
||||
"Drop file here or click to upload": {
|
||||
"One": "Déposez le fichier ici ou cliquez pour télécharger"
|
||||
},
|
||||
"Please upload csv file": {
|
||||
"One": "Veuillez télécharger le fichier csv"
|
||||
},
|
||||
"Columns": {
|
||||
"One": "Colonnes"
|
||||
},
|
||||
"You can reference export file": {
|
||||
"One": "Vous pouvez vous référer au fichier d'exportation"
|
||||
},
|
||||
"Remark": {
|
||||
"One": "Remarque"
|
||||
}
|
||||
}
|
||||
@@ -425,6 +425,15 @@
|
||||
"LastOnlineIp": {
|
||||
"One": "마지막 온라인 IP"
|
||||
},
|
||||
"OldPassword": {
|
||||
"One": "이전 비밀번호"
|
||||
},
|
||||
"NewPasswordEqualOldPassword": {
|
||||
"One": "새 비밀번호는 이전 비밀번호와 같을 수 없습니다"
|
||||
},
|
||||
"NewPassword": {
|
||||
"One": "새 비밀번호"
|
||||
},
|
||||
"ConfirmPassword": {
|
||||
"One": "비밀번호 확인"
|
||||
},
|
||||
@@ -457,5 +466,56 @@
|
||||
},
|
||||
"Captcha": {
|
||||
"One": "Captcha"
|
||||
},
|
||||
"ServerCmd": {
|
||||
"One": "서버 명령"
|
||||
},
|
||||
"Simple": {
|
||||
"One": "간단"
|
||||
},
|
||||
"Advanced": {
|
||||
"One": "고급"
|
||||
},
|
||||
"Available": {
|
||||
"One": "사용 가능"
|
||||
},
|
||||
"NotAvailable": {
|
||||
"One": "사용 불가"
|
||||
},
|
||||
"ServerCmdTips": {
|
||||
"One": "이 기능은 실험적입니다. API 및 hbbs 및 hbbr이 동일한 루프백 주소에 있는지 확인하십시오. 자세한 내용은 {wiki}를 참조하십시오."
|
||||
},
|
||||
"Refresh": {
|
||||
"One": "새로 고침"
|
||||
},
|
||||
"Save": {
|
||||
"One": "저장"
|
||||
},
|
||||
"SendCustom": {
|
||||
"One": "사용자 정의 전송"
|
||||
},
|
||||
"Send": {
|
||||
"One": "보내기"
|
||||
},
|
||||
"SendCmd": {
|
||||
"One": "명령 보내기"
|
||||
},
|
||||
"Result": {
|
||||
"One": "결과"
|
||||
},
|
||||
"Drop file here or click to upload": {
|
||||
"One": "여기에 파일을 드롭하거나 클릭하여 업로드하십시오"
|
||||
},
|
||||
"Please upload csv file": {
|
||||
"One": "csv 파일을 업로드하십시오"
|
||||
},
|
||||
"Columns": {
|
||||
"One": "열"
|
||||
},
|
||||
"You can reference export file": {
|
||||
"One": "내보내기 파일을 참조할 수 있습니다"
|
||||
},
|
||||
"Remark": {
|
||||
"One": "비고"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"One": "Данные пользователя"
|
||||
},
|
||||
"ParamRequired": {
|
||||
"One": "{param} обязательный"
|
||||
"One": "Поле {param} обязательно"
|
||||
},
|
||||
"HasBind": {
|
||||
"One": "Связано"
|
||||
@@ -42,7 +42,7 @@
|
||||
"One": "Не связано"
|
||||
},
|
||||
"UnBind": {
|
||||
"One": "Разъединить"
|
||||
"One": "Отвязать"
|
||||
},
|
||||
"ToBind": {
|
||||
"One": "Привязать"
|
||||
@@ -168,7 +168,7 @@
|
||||
"One": "Если включено, аккаунт будет автоматически зарегистрирован, когда пользователь входит через OAuth без привязки к существующему аккаунту"
|
||||
},
|
||||
"ThirdName": {
|
||||
"One": "Третье имя"
|
||||
"One": "Название стороннего сервиса"
|
||||
},
|
||||
"Close": {
|
||||
"One": "Закрыть"
|
||||
@@ -192,7 +192,7 @@
|
||||
"One": "Устройство"
|
||||
},
|
||||
"ChangeLang": {
|
||||
"One": "Сменить на китайский"
|
||||
"One": "Переключить на китайский"
|
||||
},
|
||||
"My": {
|
||||
"One": "Мой"
|
||||
@@ -431,7 +431,7 @@
|
||||
"One": "Адресная книга"
|
||||
},
|
||||
"AddressBookNameManage": {
|
||||
"One": "именами адресных книг"
|
||||
"One": "Имена адресных книг"
|
||||
},
|
||||
"MyAddressBookTips": {
|
||||
"One": "«Моя адресная книга» является стандартным для системы, не может быть изменена или удалена"
|
||||
@@ -439,6 +439,15 @@
|
||||
"LastOnlineIp": {
|
||||
"One": "Последний IP онлайн"
|
||||
},
|
||||
"OldPassword": {
|
||||
"One": "Старый пароль"
|
||||
},
|
||||
"NewPasswordEqualOldPassword": {
|
||||
"One": "Новый пароль совпадает со старым паролем"
|
||||
},
|
||||
"NewPassword": {
|
||||
"One": "Новый пароль"
|
||||
},
|
||||
"ConfirmPassword": {
|
||||
"One": "Подтвердите пароль"
|
||||
},
|
||||
@@ -471,6 +480,60 @@
|
||||
},
|
||||
"Captcha": {
|
||||
"One": "Captcha"
|
||||
},
|
||||
"ServerCmd": {
|
||||
"One": "Команда сервера"
|
||||
},
|
||||
"Simple": {
|
||||
"One": "Простой"
|
||||
},
|
||||
"Advanced": {
|
||||
"One": "Расширенный"
|
||||
},
|
||||
"Available": {
|
||||
"One": "Доступно"
|
||||
},
|
||||
"NotAvailable": {
|
||||
"One": "Недоступно"
|
||||
},
|
||||
"ServerCmdTips": {
|
||||
"One": "Эта функция экспериментальная. Пожалуйста, убедитесь, что API и hbbs и hbbr находятся на одном адресе обратной петли. Для получения дополнительной информации обратитесь к {wiki}"
|
||||
},
|
||||
"Refresh": {
|
||||
"One": "Обновить"
|
||||
},
|
||||
"Save": {
|
||||
"One": "Сохранить"
|
||||
},
|
||||
"SendCustom": {
|
||||
"One": "Отправить настраиваемое"
|
||||
},
|
||||
"Send": {
|
||||
"One": "Отправить"
|
||||
},
|
||||
"SendCmd": {
|
||||
"One": "Отправить команду"
|
||||
},
|
||||
"Result": {
|
||||
"One": "Результат"
|
||||
},
|
||||
"DeviceGroupManage": {
|
||||
"One": "Группы устройств"
|
||||
},
|
||||
"Drop file here or click to upload": {
|
||||
"One": "Перетащите файл сюда или нажмите, чтобы загрузить"
|
||||
},
|
||||
"Please upload csv file": {
|
||||
"One": "Пожалуйста, загрузите файл csv"
|
||||
},
|
||||
"Columns": {
|
||||
"One": "Столбцы"
|
||||
},
|
||||
"You can reference export file": {
|
||||
"One": "Вы можете ссылаться на экспортируемый файл"
|
||||
},
|
||||
"Remark": {
|
||||
"One": "Примечание"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -446,6 +446,9 @@
|
||||
"NewPassword": {
|
||||
"One": "新密码"
|
||||
},
|
||||
"NewPasswordEqualOldPassword": {
|
||||
"One": "新密码不能与旧密码相同"
|
||||
},
|
||||
"ConfirmPassword": {
|
||||
"One": "确认密码"
|
||||
},
|
||||
@@ -487,5 +490,62 @@
|
||||
},
|
||||
"Captcha": {
|
||||
"One": "验证码"
|
||||
},
|
||||
"ServerCmd": {
|
||||
"One": "服务端命令"
|
||||
},
|
||||
"Simple": {
|
||||
"One": "简单"
|
||||
},
|
||||
"Advanced": {
|
||||
"One": "高级"
|
||||
},
|
||||
"Available": {
|
||||
"One": "可用"
|
||||
},
|
||||
"NotAvailable": {
|
||||
"One": "不可用"
|
||||
},
|
||||
"ServerCmdTips": {
|
||||
"One": "此处功能为实验性质,请确保api和 hbbs/hbbr 处于同一回环地址下,更多说明请参考 {wiki}"
|
||||
},
|
||||
"Refresh": {
|
||||
"One": "刷新"
|
||||
},
|
||||
"Save": {
|
||||
"One": "保存"
|
||||
},
|
||||
"SendCustom": {
|
||||
"One": "发送自定义"
|
||||
},
|
||||
"Send": {
|
||||
"One": "发送"
|
||||
},
|
||||
"SendCmd": {
|
||||
"One": "发送命令"
|
||||
},
|
||||
"Result": {
|
||||
"One": "结果"
|
||||
},
|
||||
"DeviceGroupManage": {
|
||||
"One": "设备组管理"
|
||||
},
|
||||
"Import": {
|
||||
"One": "导入"
|
||||
},
|
||||
"Drop file here or click to upload": {
|
||||
"One": "将文件拖到此处或单击上传"
|
||||
},
|
||||
"Please upload csv file": {
|
||||
"One": "请上传csv文件"
|
||||
},
|
||||
"Columns": {
|
||||
"One": "列"
|
||||
},
|
||||
"You can reference export file": {
|
||||
"One": "您可以参考导出文件"
|
||||
},
|
||||
"Remark": {
|
||||
"One": "备注"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,551 @@
|
||||
{
|
||||
"Login": {
|
||||
"One": "登入"
|
||||
},
|
||||
"Logout": {
|
||||
"One": "退出登入"
|
||||
},
|
||||
"Register": {
|
||||
"One": "註冊"
|
||||
},
|
||||
"Confirm": {
|
||||
"One": "確認"
|
||||
},
|
||||
"Username": {
|
||||
"One": "使用者名稱"
|
||||
},
|
||||
"Password": {
|
||||
"One": "密碼"
|
||||
},
|
||||
"LoginSuccess": {
|
||||
"One": "登入成功"
|
||||
},
|
||||
"ForgotPassword": {
|
||||
"One": "忘記密碼"
|
||||
},
|
||||
"ResetPassword": {
|
||||
"One": "重置密碼"
|
||||
},
|
||||
"ChangePassword": {
|
||||
"One": "修改密碼"
|
||||
},
|
||||
"Userinfo": {
|
||||
"One": "使用者資訊"
|
||||
},
|
||||
"ParamRequired": {
|
||||
"One": "{param} 是必須的"
|
||||
},
|
||||
"HasBind": {
|
||||
"One": "已繫結"
|
||||
},
|
||||
"NoBind": {
|
||||
"One": "未繫結"
|
||||
},
|
||||
"UnBind": {
|
||||
"One": "解綁"
|
||||
},
|
||||
"ToBind": {
|
||||
"One": "繫結"
|
||||
},
|
||||
"Confirm?": {
|
||||
"One": "確定{param}?"
|
||||
},
|
||||
"Cancel": {
|
||||
"One": "取消"
|
||||
},
|
||||
"Platform": {
|
||||
"One": "平臺"
|
||||
},
|
||||
"Status": {
|
||||
"One": "狀態"
|
||||
},
|
||||
"Actions": {
|
||||
"One": "操作"
|
||||
},
|
||||
"Filter": {
|
||||
"One": "篩選"
|
||||
},
|
||||
"Add": {
|
||||
"One": "新增"
|
||||
},
|
||||
"Hostname": {
|
||||
"One": "主機名"
|
||||
},
|
||||
"Alias": {
|
||||
"One": "別名"
|
||||
},
|
||||
"Hash": {
|
||||
"One": "雜湊"
|
||||
},
|
||||
"Tags": {
|
||||
"One": "標籤"
|
||||
},
|
||||
"Edit": {
|
||||
"One": "編輯"
|
||||
},
|
||||
"Delete": {
|
||||
"One": "刪除"
|
||||
},
|
||||
"Create": {
|
||||
"One": "建立"
|
||||
},
|
||||
"Update": {
|
||||
"One": "更新"
|
||||
},
|
||||
"LoginName": {
|
||||
"One": "登入名"
|
||||
},
|
||||
"Submit": {
|
||||
"One": "提交"
|
||||
},
|
||||
"OperationSuccess": {
|
||||
"One": "操作成功"
|
||||
},
|
||||
"Owner": {
|
||||
"One": "所有者"
|
||||
},
|
||||
"Name": {
|
||||
"One": "名稱"
|
||||
},
|
||||
"Color": {
|
||||
"One": "顏色"
|
||||
},
|
||||
"CreatedAt": {
|
||||
"One": "建立時間"
|
||||
},
|
||||
"UpdatedAt": {
|
||||
"One": "更新時間"
|
||||
},
|
||||
"Memory": {
|
||||
"One": "記憶體"
|
||||
},
|
||||
"Os": {
|
||||
"One": "作業系統"
|
||||
},
|
||||
"Uuid": {
|
||||
"One": "UUID"
|
||||
},
|
||||
"Version": {
|
||||
"One": "版本"
|
||||
},
|
||||
"Type": {
|
||||
"One": "型別"
|
||||
},
|
||||
"Group": {
|
||||
"One": "組"
|
||||
},
|
||||
"CommonGroup": {
|
||||
"One": "普通組"
|
||||
},
|
||||
"CommonGroupNote": {
|
||||
"One": "只有管理員才能檢視群組成員及其裝置"
|
||||
},
|
||||
"SharedGroup": {
|
||||
"One": "共享組"
|
||||
},
|
||||
"SharedGroupNote": {
|
||||
"One": "所有使用者都可以檢視群組成員及其裝置"
|
||||
},
|
||||
"Nickname": {
|
||||
"One": "暱稱"
|
||||
},
|
||||
"UserTags": {
|
||||
"One": "使用者標籤"
|
||||
},
|
||||
"UserAddressBook": {
|
||||
"One": "使用者地址簿"
|
||||
},
|
||||
"IsAdmin": {
|
||||
"One": "是否管理員"
|
||||
},
|
||||
"PleaseInputNewPassword": {
|
||||
"One": "請輸入新密碼"
|
||||
},
|
||||
"AutoRegister": {
|
||||
"One": "自動註冊"
|
||||
},
|
||||
"AutoRegisterNote": {
|
||||
"One": "如果啟用,則當用戶使用 OAuth 登入時,將自動註冊一個帳戶,而無需繫結現有帳戶"
|
||||
},
|
||||
"ThirdName": {
|
||||
"One": "第三方名稱"
|
||||
},
|
||||
"Close": {
|
||||
"One": "關閉"
|
||||
},
|
||||
"OauthBinding": {
|
||||
"One": "您正在授權繫結"
|
||||
},
|
||||
"OauthLogining": {
|
||||
"One": "您正在授權登入"
|
||||
},
|
||||
"OauthCloseNote": {
|
||||
"One": "如不是您發起的授權,請直接關閉頁面"
|
||||
},
|
||||
"OperationSuccessAndCloseAfter3Seconds": {
|
||||
"One": "操作成功,3秒後將自動關閉頁面"
|
||||
},
|
||||
"ConfirmOauth": {
|
||||
"One": "確認授權"
|
||||
},
|
||||
"Device": {
|
||||
"One": "裝置"
|
||||
},
|
||||
"ChangeLang": {
|
||||
"One": "ToEnglish"
|
||||
},
|
||||
"My": {
|
||||
"One": "我的"
|
||||
},
|
||||
"Info": {
|
||||
"One": "資訊"
|
||||
},
|
||||
"AddressBooks": {
|
||||
"One": "地址簿"
|
||||
},
|
||||
"System": {
|
||||
"One": "系統"
|
||||
},
|
||||
"PeerManage": {
|
||||
"One": "裝置管理"
|
||||
},
|
||||
"AddressBookManage": {
|
||||
"One": "地址簿管理"
|
||||
},
|
||||
"GroupManage": {
|
||||
"One": "群組管理"
|
||||
},
|
||||
"UserManage": {
|
||||
"One": "使用者管理"
|
||||
},
|
||||
"UserAdd": {
|
||||
"One": "使用者新增"
|
||||
},
|
||||
"UserEdit": {
|
||||
"One": "使用者編輯"
|
||||
},
|
||||
"TagsManage": {
|
||||
"One": "標籤管理"
|
||||
},
|
||||
"OauthManage": {
|
||||
"One": "Oauth 管理"
|
||||
},
|
||||
"LoginLog": {
|
||||
"One": "登入日誌"
|
||||
},
|
||||
"LastOnlineTime": {
|
||||
"One": "最後線上時間"
|
||||
},
|
||||
"JustNow": {
|
||||
"One": "剛剛"
|
||||
},
|
||||
"MinutesAgo": {
|
||||
"One": "{param} 分鐘前"
|
||||
},
|
||||
"HoursAgo": {
|
||||
"One": "{param} 小時前"
|
||||
},
|
||||
"DaysAgo": {
|
||||
"One": "{param} 天前"
|
||||
},
|
||||
"MonthsAgo": {
|
||||
"One": "{param} 月前"
|
||||
},
|
||||
"YearsAgo": {
|
||||
"One": "{param} 年前"
|
||||
},
|
||||
"MinutesLess": {
|
||||
"One": "{param} 分鐘內"
|
||||
},
|
||||
"HoursLess": {
|
||||
"One": "{param} 小時內"
|
||||
},
|
||||
"DaysLess": {
|
||||
"One": "{param} 天內"
|
||||
},
|
||||
"Export": {
|
||||
"One": "匯出"
|
||||
},
|
||||
"AddToAddressBook": {
|
||||
"One": "新增到地址簿"
|
||||
},
|
||||
"BatchDelete": {
|
||||
"One": "批量刪除"
|
||||
},
|
||||
"PleaseSelectData": {
|
||||
"One": "請選擇資料"
|
||||
},
|
||||
"PasswordType": {
|
||||
"One": "密碼型別"
|
||||
},
|
||||
"OncePassword": {
|
||||
"One": "一次性密碼"
|
||||
},
|
||||
"FixedPassword": {
|
||||
"One": "固定密碼"
|
||||
},
|
||||
"FixedPasswordWarning": {
|
||||
"One": "固定密碼可能存在洩露風險,請謹慎使用,建議使用一次性密碼"
|
||||
},
|
||||
"ExpireTime": {
|
||||
"One": "過期時間"
|
||||
},
|
||||
"ShareByWebClient": {
|
||||
"One": "通過 Web Client 分享"
|
||||
},
|
||||
"Minutes": {
|
||||
"One": "{param} 分鐘"
|
||||
},
|
||||
"Hours": {
|
||||
"One": "{param} 小時"
|
||||
},
|
||||
"Days": {
|
||||
"One": "{param} 天"
|
||||
},
|
||||
"Weeks": {
|
||||
"One": "{param} 周"
|
||||
},
|
||||
"Months": {
|
||||
"One": "{param} 月"
|
||||
},
|
||||
"Forever": {
|
||||
"One": "永久"
|
||||
},
|
||||
"Error": {
|
||||
"One": "錯誤"
|
||||
},
|
||||
"IDNotExist": {
|
||||
"One": "ID 不存在"
|
||||
},
|
||||
"RemoteDesktopOffline": {
|
||||
"One": "遠端電腦不線上"
|
||||
},
|
||||
"KeyMismatch": {
|
||||
"One": "KEY 不匹配"
|
||||
},
|
||||
"KeyOveruse": {
|
||||
"One": "KEY 使用過度"
|
||||
},
|
||||
"Link": {
|
||||
"One": "連結"
|
||||
},
|
||||
"CopySuccess": {
|
||||
"One": "複製成功"
|
||||
},
|
||||
"CopyFailed": {
|
||||
"One": "複製失敗"
|
||||
},
|
||||
"Timeout": {
|
||||
"One": "超時"
|
||||
},
|
||||
"AuditConnLog": {
|
||||
"One": "連線日誌"
|
||||
},
|
||||
"Peer": {
|
||||
"One": "裝置"
|
||||
},
|
||||
"FromPeer": {
|
||||
"One": "來源裝置"
|
||||
},
|
||||
"FromName": {
|
||||
"One": "來源名稱"
|
||||
},
|
||||
"CloseTime": {
|
||||
"One": "關閉時間"
|
||||
},
|
||||
"AuditFileLog": {
|
||||
"One": "檔案日誌"
|
||||
},
|
||||
"Common": {
|
||||
"One": "普通"
|
||||
},
|
||||
"File": {
|
||||
"One": "檔案"
|
||||
},
|
||||
"Num": {
|
||||
"One": "數量"
|
||||
},
|
||||
"Ip": {
|
||||
"One": "IP"
|
||||
},
|
||||
"FileName": {
|
||||
"One": "檔名"
|
||||
},
|
||||
"FileInfo": {
|
||||
"One": "檔案資訊"
|
||||
},
|
||||
"Path": {
|
||||
"One": "路徑"
|
||||
},
|
||||
"IndexNum": {
|
||||
"One": "序號"
|
||||
},
|
||||
"ToRemote": {
|
||||
"One": "到遠端"
|
||||
},
|
||||
"ToLocal": {
|
||||
"One": "到本地"
|
||||
},
|
||||
"AddressBookName": {
|
||||
"One": "地址簿名稱"
|
||||
},
|
||||
"AddressBookCollection": {
|
||||
"One": "地址簿集"
|
||||
},
|
||||
"AddRule": {
|
||||
"One": "新增規則"
|
||||
},
|
||||
"ShareRules": {
|
||||
"One": "分享規則"
|
||||
},
|
||||
"Rule": {
|
||||
"One": "規則"
|
||||
},
|
||||
"Read": {
|
||||
"One": "讀取"
|
||||
},
|
||||
"ReadWrite": {
|
||||
"One": "讀寫"
|
||||
},
|
||||
"FullControl": {
|
||||
"One": "完全控制"
|
||||
},
|
||||
"ShareTo": {
|
||||
"One": "分享給"
|
||||
},
|
||||
"MyAddressBook": {
|
||||
"One": "我的地址簿"
|
||||
},
|
||||
"AddressBook": {
|
||||
"One": "地址簿"
|
||||
},
|
||||
"AddressBookNameManage": {
|
||||
"One": "地址簿名稱"
|
||||
},
|
||||
"MyAddressBookTips": {
|
||||
"One": "\"我的地址簿\"是系統預設,不可修改、刪除"
|
||||
},
|
||||
"LastOnlineIp": {
|
||||
"One": "最後線上 IP"
|
||||
},
|
||||
"or login in with": {
|
||||
"One": "或使用以下登陸"
|
||||
},
|
||||
"Optional, default is": {
|
||||
"One": "可選, 預設值是"
|
||||
},
|
||||
"Check your IdP docs, without": {
|
||||
"One": "檢查 IdP 的文件, 不包含"
|
||||
},
|
||||
"For OIDC login without a password, enter any 4-20 letters": {
|
||||
"One": "如果通過 OIDC 登入且未設定密碼,請輸入任意 4-20 個字元"
|
||||
},
|
||||
"OldPassword": {
|
||||
"One": "舊密碼"
|
||||
},
|
||||
"NewPassword": {
|
||||
"One": "新密碼"
|
||||
},
|
||||
"NewPasswordEqualOldPassword": {
|
||||
"One": "新密碼不能與舊密碼相同"
|
||||
},
|
||||
"ConfirmPassword": {
|
||||
"One": "確認密碼"
|
||||
},
|
||||
"PasswordNotMatchConfirmPassword": {
|
||||
"One": "密碼與確認密碼不匹配"
|
||||
},
|
||||
"ToLogin": {
|
||||
"One": "去登入"
|
||||
},
|
||||
"UserToken": {
|
||||
"One": "使用者 Token"
|
||||
},
|
||||
"Email": {
|
||||
"One": "電子信箱"
|
||||
},
|
||||
"MyPeer": {
|
||||
"One": "我的裝置"
|
||||
},
|
||||
"View": {
|
||||
"One": "檢視"
|
||||
},
|
||||
"Information": {
|
||||
"One": "資訊"
|
||||
},
|
||||
"BatchAddToAB": {
|
||||
"One": "批量新增到地址簿"
|
||||
},
|
||||
"BatchEditTags": {
|
||||
"One": "批量編輯標籤"
|
||||
},
|
||||
"User": {
|
||||
"One": "使用者"
|
||||
},
|
||||
"ShareRecord": {
|
||||
"One": "分享記錄"
|
||||
},
|
||||
"Second": {
|
||||
"One": "秒"
|
||||
},
|
||||
"Captcha": {
|
||||
"One": "驗證碼"
|
||||
},
|
||||
"ServerCmd": {
|
||||
"One": "服務端命令"
|
||||
},
|
||||
"Simple": {
|
||||
"One": "簡單"
|
||||
},
|
||||
"Advanced": {
|
||||
"One": "高階"
|
||||
},
|
||||
"Available": {
|
||||
"One": "可用"
|
||||
},
|
||||
"NotAvailable": {
|
||||
"One": "不可用"
|
||||
},
|
||||
"ServerCmdTips": {
|
||||
"One": "此處功能為實驗性質,請確保 API 和 hbbs/hbbr 處於同一迴環地址下,更多說明請參考 {wiki}"
|
||||
},
|
||||
"Refresh": {
|
||||
"One": "重新整理"
|
||||
},
|
||||
"Save": {
|
||||
"One": "儲存"
|
||||
},
|
||||
"SendCustom": {
|
||||
"One": "傳送自定義"
|
||||
},
|
||||
"Send": {
|
||||
"One": "傳送"
|
||||
},
|
||||
"SendCmd": {
|
||||
"One": "傳送命令"
|
||||
},
|
||||
"Result": {
|
||||
"One": "結果"
|
||||
},
|
||||
"DeviceGroupManage": {
|
||||
"One": "設備組管理"
|
||||
},
|
||||
"Import": {
|
||||
"One": "導入"
|
||||
},
|
||||
"Drop file here or click to upload": {
|
||||
"One": "將檔案拖到這裡,或點擊上傳"
|
||||
},
|
||||
"Please upload csv file": {
|
||||
"One": "請上傳 csv 檔案"
|
||||
},
|
||||
"Columns": {
|
||||
"One": "欄位"
|
||||
},
|
||||
"You can reference export file": {
|
||||
"One": "您可以參考匯出檔案"
|
||||
},
|
||||
"Remark": {
|
||||
"One": "備註"
|
||||
}
|
||||
}
|
||||
Fichier diff supprimé car celui-ci est trop grand
Voir la Diff
@@ -1,40 +1,22 @@
|
||||
import { ref } from 'vue'
|
||||
import { server } from '@/api/config'
|
||||
import Websock from '@/utils/webclient/websock'
|
||||
import * as rendezvous from '@/utils/webclient/rendezvous'
|
||||
import * as message from '@/utils/webclient/message'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { T } from '@/utils/i18n'
|
||||
import { useAppStore } from '@/store/app'
|
||||
|
||||
const prefix = 'wc-'
|
||||
|
||||
|
||||
const app = useAppStore()
|
||||
|
||||
export const toWebClientLink = (row) => {
|
||||
//v2
|
||||
window.open(`${rustdeskConfig.value.api_server}/webclient2/#/${row.id}`)
|
||||
console.log(app.setting.rustdeskConfig)
|
||||
window.open(`${app.setting.rustdeskConfig.api_server}/webclient2/#/${row.id}`)
|
||||
}
|
||||
|
||||
export const rustdeskConfig = ref({})
|
||||
|
||||
export async function loadRustdeskConfig () {
|
||||
console.log('loadRustdeskConfig')
|
||||
if (rustdeskConfig.value.id_server === undefined || rustdeskConfig.value.key === undefined) {
|
||||
const res = await server().catch(_ => false)
|
||||
if (res) {
|
||||
rustdeskConfig.value = res.data
|
||||
localStorage.setItem(`${prefix}custom-rendezvous-server`, res.data.id_server)
|
||||
localStorage.setItem(`${prefix}key`, res.data.key)
|
||||
localStorage.setItem(`${prefix}api-server`, res.data.api_server)
|
||||
}
|
||||
}
|
||||
return {
|
||||
rustdeskConfig,
|
||||
}
|
||||
}
|
||||
|
||||
loadRustdeskConfig()
|
||||
|
||||
export async function getPeerSlat (id) {
|
||||
const [addr, port] = rustdeskConfig.value.id_server.split(':')
|
||||
const [addr, port] = app.setting.rustdeskConfig.id_server.split(':')
|
||||
if (!addr) {
|
||||
return
|
||||
}
|
||||
@@ -45,7 +27,7 @@ export async function getPeerSlat (id) {
|
||||
const nat_type = rendezvous.NatType.SYMMETRIC
|
||||
const punch_hole_request = rendezvous.PunchHoleRequest.fromPartial({
|
||||
id,
|
||||
licence_key: rustdeskConfig.value.key || undefined,
|
||||
licence_key: app.setting.rustdeskConfig.value.key || undefined,
|
||||
conn_type,
|
||||
nat_type,
|
||||
token: undefined,
|
||||
@@ -87,7 +69,7 @@ export async function getPeerSlat (id) {
|
||||
await _ws.open()
|
||||
console.log(new Date() + ': Connected to relay server')
|
||||
const request_relay = rendezvous.RequestRelay.fromPartial({
|
||||
licence_key: rustdeskConfig.value.key || undefined,
|
||||
licence_key: app.setting.rustdeskConfig.key || undefined,
|
||||
uuid,
|
||||
})
|
||||
_ws.sendRendezvous({ request_relay })
|
||||
@@ -112,5 +94,5 @@ export async function getPeerSlat (id) {
|
||||
}
|
||||
|
||||
export function getV2ShareUrl (token) {
|
||||
return `${rustdeskConfig.value.api_server}/webclient2/#/?share_token=${token}`
|
||||
return `${app.setting.rustdeskConfig.api_server}/webclient2/#/?share_token=${token}`
|
||||
}
|
||||
|
||||
@@ -59,13 +59,20 @@ export function useRepositories (api_type = 'my') {
|
||||
{ label: T('ReadWrite'), value: 2 },
|
||||
{ label: T('FullControl'), value: 3 },
|
||||
])
|
||||
|
||||
const TYPE_U = 1
|
||||
const TYPE_G = 2
|
||||
const types = computed(_ => [
|
||||
{ label: T('Group'), value: TYPE_G },
|
||||
{ label: T('User'), value: TYPE_U },
|
||||
])
|
||||
const formVisible = ref(false)
|
||||
const formData = reactive({
|
||||
id: 0,
|
||||
collection_id: null,
|
||||
type: 1,
|
||||
type: TYPE_U,
|
||||
rule: 1,
|
||||
g_id: null,
|
||||
u_id: null,
|
||||
to_id: null,
|
||||
user_id: null,
|
||||
})
|
||||
@@ -76,27 +83,59 @@ export function useRepositories (api_type = 'my') {
|
||||
Object.keys(formData).forEach(key => {
|
||||
formData[key] = row[key]
|
||||
})
|
||||
|
||||
if (row.type === TYPE_U) {
|
||||
formData.u_id = row.to_id
|
||||
formData.g_id = users.value.find(u => u.id === row.to_id)?.group_id
|
||||
} else {
|
||||
formData.g_id = row.to_id
|
||||
formData.u_id = null
|
||||
}
|
||||
}
|
||||
const toAdd = () => {
|
||||
//初始化formData
|
||||
formData.id = 0
|
||||
formData.type = TYPE_U
|
||||
formData.rule = 1
|
||||
formData.g_id = null
|
||||
formData.u_id = null
|
||||
|
||||
formVisible.value = true
|
||||
|
||||
}
|
||||
const submit = async () => {
|
||||
const api = formData.id ? apis[api_type].update : apis[api_type].create
|
||||
const res = await api(formData).catch(_ => false)
|
||||
const form = {
|
||||
...formData,
|
||||
}
|
||||
form.to_id = form.type === TYPE_G ? form.g_id : form.u_id
|
||||
const res = await api(form).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
formVisible.value = false
|
||||
getList()
|
||||
}
|
||||
}
|
||||
const groupUsersList = ref([])
|
||||
const groups = ref([])
|
||||
const users = ref([])
|
||||
const getGroupUsers = async () => {
|
||||
const res = await groupUsers({ user_id: formData.user_id }).catch(_ => false)
|
||||
const res = await groupUsers().catch(_ => false)
|
||||
if (res) {
|
||||
groupUsersList.value = res.data
|
||||
groups.value = res.data.groups.map(item => {
|
||||
if (!item.children) {
|
||||
item.children = []
|
||||
}
|
||||
res.data.users.map(u => {
|
||||
if (item.id === u.group_id) {
|
||||
item.children.push(u)
|
||||
}
|
||||
})
|
||||
return item
|
||||
})
|
||||
users.value = res.data.users
|
||||
}
|
||||
}
|
||||
const changeGId = () => {
|
||||
formData.u_id = null
|
||||
}
|
||||
return {
|
||||
listRes,
|
||||
@@ -110,7 +149,12 @@ export function useRepositories (api_type = 'my') {
|
||||
toAdd,
|
||||
submit,
|
||||
rules,
|
||||
groupUsersList,
|
||||
types,
|
||||
groups,
|
||||
users,
|
||||
getGroupUsers,
|
||||
TYPE_G,
|
||||
TYPE_U,
|
||||
changeGId,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,20 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" :label="T('Type')" align="center">
|
||||
<template #default="{row}">
|
||||
<div>
|
||||
{{ types.find(t => t.value === row.type)?.label }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="to_id" :label="T('ShareTo')" align="center">
|
||||
<template #default="{row}">
|
||||
<div v-if="row.type===1">
|
||||
{{ groupUsersList.find(u => u.id === row.to_id)?.username }}
|
||||
<div v-if="row.type===TYPE_U">
|
||||
{{ users.find(u => u.id === row.to_id)?.username }}
|
||||
</div>
|
||||
<div v-else-if="row.type===TYPE_G">
|
||||
{{ groups.find(g => g.id === row.to_id)?.name }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -50,22 +60,40 @@
|
||||
</el-form-item>
|
||||
<el-form-item :label="T('Rule')" prop="rule" required>
|
||||
<el-radio-group v-model="formData.rule">
|
||||
<el-radio v-for="item in rules" :key="item.value" :label="item.value">
|
||||
<el-radio v-for="item in rules" :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="T('ShareTo')" prop="to_id" required>
|
||||
<el-form-item :label="T('Type')" prop="type" required>
|
||||
<el-radio-group v-model="formData.type">
|
||||
<el-radio v-for="item in types" :key="item.value" :value="parseInt(item.value)">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="T('ShareTo')" prop="g_id" required>
|
||||
<!-- <el-input-number v-model="formData.to_id"></el-input-number>-->
|
||||
<el-select v-model="formData.to_id">
|
||||
<div style="width: 30%">
|
||||
<el-select v-model="formData.g_id" @change="changeGId">
|
||||
<el-option
|
||||
v-for="item in groupUsersList"
|
||||
v-for="item in groups"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div style="width: 30%;margin-left: 20px">
|
||||
<el-select v-model="formData.u_id" v-if="formData.type===TYPE_U">
|
||||
<el-option
|
||||
v-for="item in users.filter(u => u.group_id === formData.g_id)"
|
||||
:key="item.id"
|
||||
:label="item.username"
|
||||
:value="item.id"
|
||||
:disabled="!item.status"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="formVisible = false">{{ T('Cancel') }}</el-button>
|
||||
@@ -104,8 +132,13 @@
|
||||
toAdd,
|
||||
submit,
|
||||
rules,
|
||||
groupUsersList,
|
||||
types,
|
||||
groups,
|
||||
users,
|
||||
getGroupUsers,
|
||||
TYPE_G,
|
||||
TYPE_U,
|
||||
changeGId,
|
||||
} = useRepositories(props.is_my ? 'my' : 'admin')
|
||||
|
||||
formData.collection_id = props.collection.id
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handlerQuery">{{ T('Filter') }}</el-button>
|
||||
<el-button type="danger" @click="toBatchDelete">{{ T('BatchDelete') }}</el-button>
|
||||
<el-button type="success" @click="toExport">{{ T('Export') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
@@ -62,6 +63,7 @@
|
||||
handlerQuery,
|
||||
del,
|
||||
batchdel,
|
||||
toExport,
|
||||
} = useRepositories()
|
||||
|
||||
onMounted(getList)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handlerQuery">{{ T('Filter') }}</el-button>
|
||||
<el-button type="danger" @click="toBatchDelete">{{ T('BatchDelete') }}</el-button>
|
||||
<el-button type="success" @click="toExport">{{ T('Export') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
@@ -109,6 +110,7 @@
|
||||
handlerQuery,
|
||||
del,
|
||||
batchdel,
|
||||
toExport,
|
||||
} = useFileRepositories()
|
||||
|
||||
onMounted(getList)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { reactive } from 'vue'
|
||||
import { list, remove, fileList, fileRemove, batchDelete, fileBatchDelete } from '@/api/audit'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { formatTime } from '@/utils/time'
|
||||
import { T } from '@/utils/i18n'
|
||||
import { downBlob, jsonToCsv } from '@/utils/file'
|
||||
|
||||
export function useRepositories () {
|
||||
const listRes = reactive({
|
||||
@@ -73,6 +73,17 @@ export function useRepositories () {
|
||||
getList()
|
||||
}
|
||||
}
|
||||
|
||||
const toExport = async () => {
|
||||
const q = { ...listQuery }
|
||||
q.page_size = 1000000
|
||||
q.page = 1
|
||||
const res = await list(q).catch(_ => false)
|
||||
if (res) {
|
||||
const csv = jsonToCsv(res.data.list)
|
||||
downBlob(csv, 'connectLog.csv')
|
||||
}
|
||||
}
|
||||
return {
|
||||
listRes,
|
||||
listQuery,
|
||||
@@ -80,6 +91,7 @@ export function useRepositories () {
|
||||
handlerQuery,
|
||||
del,
|
||||
batchdel,
|
||||
toExport,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +163,18 @@ export function useFileRepositories () {
|
||||
getList()
|
||||
}
|
||||
}
|
||||
|
||||
const toExport = async () => {
|
||||
const q = { ...listQuery }
|
||||
q.page_size = 1000000
|
||||
q.page = 1
|
||||
const res = await fileList(q).catch(_ => false)
|
||||
if (res) {
|
||||
const csv = jsonToCsv(res.data.list)
|
||||
downBlob(csv, 'fileTransformLog.csv')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
listRes,
|
||||
listQuery,
|
||||
@@ -158,5 +182,6 @@ export function useFileRepositories () {
|
||||
handlerQuery,
|
||||
del,
|
||||
batchdel,
|
||||
toExport,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="list-query" shadow="hover">
|
||||
<el-form inline label-width="80px">
|
||||
<!-- <el-form-item label="名称">
|
||||
<el-input v-model="listQuery.name"></el-input>
|
||||
</el-form-item>-->
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handlerQuery">{{ T('Filter') }}</el-button>
|
||||
<el-button type="danger" @click="toAdd">{{ T('Add') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="list-body" shadow="hover">
|
||||
<el-table :data="listRes.list" v-loading="listRes.loading" border>
|
||||
<el-table-column prop="id" label="ID" align="center"></el-table-column>
|
||||
<el-table-column prop="name" :label="T('Name')" align="center"/>
|
||||
<el-table-column prop="created_at" :label="T('CreatedAt')" align="center"/>
|
||||
<el-table-column prop="updated_at" :label="T('UpdatedAt')" align="center"/>
|
||||
<el-table-column :label="T('Actions')" align="center">
|
||||
<template #default="{row}">
|
||||
<el-button @click="toEdit(row)">{{ T('Edit') }}</el-button>
|
||||
<el-button type="danger" @click="del(row)">{{ T('Delete') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<el-card class="list-page" shadow="hover">
|
||||
<el-pagination background
|
||||
layout="prev, pager, next, sizes, jumper"
|
||||
:page-sizes="[10,20,50,100]"
|
||||
v-model:page-size="listQuery.page_size"
|
||||
v-model:current-page="listQuery.page"
|
||||
:total="listRes.total">
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
<el-dialog v-model="formVisible" :title="!formData.id?T('Create'):T('Update')" width="800">
|
||||
<el-form class="dialog-form" ref="form" :model="formData" label-width="120px">
|
||||
<el-form-item :label="T('Name')" prop="name" required>
|
||||
<el-input v-model="formData.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="formVisible = false">{{ T('Cancel') }}</el-button>
|
||||
<el-button @click="submit" type="primary">{{ T('Submit') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, watch, ref, onActivated } from 'vue'
|
||||
import { list, create, update, detail, remove } from '@/api/device_group'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { T } from '@/utils/i18n'
|
||||
|
||||
const listRes = reactive({
|
||||
list: [], total: 0, loading: false,
|
||||
})
|
||||
const listQuery = reactive({
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
})
|
||||
|
||||
const getList = async () => {
|
||||
listRes.loading = true
|
||||
const res = await list(listQuery).catch(_ => false)
|
||||
listRes.loading = false
|
||||
if (res) {
|
||||
listRes.list = res.data.list
|
||||
listRes.total = res.data.total
|
||||
}
|
||||
}
|
||||
const handlerQuery = () => {
|
||||
if (listQuery.page === 1) {
|
||||
getList()
|
||||
} else {
|
||||
listQuery.page = 1
|
||||
}
|
||||
}
|
||||
|
||||
const del = async (row) => {
|
||||
const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('Delete') }), {
|
||||
confirmButtonText: T('Confirm'),
|
||||
cancelButtonText: T('Cancel'),
|
||||
type: 'warning',
|
||||
}).catch(_ => false)
|
||||
if (!cf) {
|
||||
return false
|
||||
}
|
||||
|
||||
const res = await remove({ id: row.id }).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
getList()
|
||||
}
|
||||
}
|
||||
onMounted(getList)
|
||||
onActivated(getList)
|
||||
|
||||
watch(() => listQuery.page, getList)
|
||||
|
||||
watch(() => listQuery.page_size, handlerQuery)
|
||||
|
||||
const formVisible = ref(false)
|
||||
const formData = reactive({
|
||||
id: 0,
|
||||
name: '',
|
||||
type: 1,
|
||||
})
|
||||
|
||||
const toEdit = (row) => {
|
||||
formVisible.value = true
|
||||
formData.id = row.id
|
||||
formData.name = row.name
|
||||
formData.type = row.type
|
||||
}
|
||||
const toAdd = () => {
|
||||
formVisible.value = true
|
||||
formData.id = 0
|
||||
formData.name = ''
|
||||
formData.type = 1
|
||||
}
|
||||
const submit = async () => {
|
||||
const api = formData.id ? update : create
|
||||
const res = await api(formData).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
formVisible.value = false
|
||||
getList()
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -1,13 +1,18 @@
|
||||
import { reactive, ref } from 'vue'
|
||||
import { batchDelete, list, remove } from '@/api/login_log'
|
||||
import { list as fetchPeers } from '@/api/peer'
|
||||
import { list as admin_fetchPeers } from '@/api/peer'
|
||||
import { list as my_fetchPeers } from '@/api/my/peer'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { T } from '@/utils/i18n'
|
||||
import { batchDelete as admin_batchDelete, list as admin_list, remove as admin_remove } from '@/api/login_log'
|
||||
import { batchDelete as my_batchDelete, list as my_list, remove as my_remove } from '@/api/my/login_log'
|
||||
import { downBlob, jsonToCsv } from '@/utils/file'
|
||||
|
||||
export function useRepositories () {
|
||||
const route = useRoute()
|
||||
const user_id = route.query?.user_id
|
||||
const apis = {
|
||||
admin: { batchDelete: admin_batchDelete, list: admin_list, remove: admin_remove, fetchPeers: admin_fetchPeers },
|
||||
my: { batchDelete: my_batchDelete, list: my_list, remove: my_remove, fetchPeers: my_fetchPeers },
|
||||
}
|
||||
|
||||
export function useRepositories (api_type = 'my') {
|
||||
|
||||
const listRes = reactive({
|
||||
list: [], total: 0, loading: false,
|
||||
@@ -16,16 +21,20 @@ export function useRepositories () {
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
is_my: 0,
|
||||
user_id: user_id ? parseInt(user_id) : null,
|
||||
user_id: null,
|
||||
})
|
||||
|
||||
const getList = async () => {
|
||||
listRes.loading = true
|
||||
const res = await list(listQuery).catch(_ => false)
|
||||
const res = await apis[api_type].list(listQuery).catch(_ => false)
|
||||
listRes.loading = false
|
||||
if (res) {
|
||||
const uuids = res.data.list.filter(item => item.uuid).map(item => item.uuid)
|
||||
const peers = await fetchPeers({ uuids }).catch(_ => false)
|
||||
//通过uuid补全peer信息
|
||||
const uuids = res.data.list.filter(item => item.uuid && item.client === 'client' && !item.device_id).map(item => item.uuid)
|
||||
if (uuids.length > 0) {
|
||||
//uuids去重
|
||||
const uniqueUuids = [...new Set(uuids)]
|
||||
const peers = await apis[api_type].fetchPeers({ uuids: uniqueUuids }).catch(_ => false)
|
||||
if (peers?.data?.list) {
|
||||
res.data.list.forEach(item => {
|
||||
if (item.uuid) {
|
||||
@@ -33,6 +42,8 @@ export function useRepositories () {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
listRes.list = res.data.list
|
||||
listRes.total = res.data.total
|
||||
}
|
||||
@@ -55,7 +66,7 @@ export function useRepositories () {
|
||||
return false
|
||||
}
|
||||
|
||||
const res = await remove({ id: row.id }).catch(_ => false)
|
||||
const res = await apis[api_type].remove({ id: row.id }).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
getList()
|
||||
@@ -77,13 +88,28 @@ export function useRepositories () {
|
||||
return false
|
||||
}
|
||||
|
||||
const res = await batchDelete({ ids }).catch(_ => false)
|
||||
const res = await apis[api_type].batchDelete({ ids }).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
getList()
|
||||
}
|
||||
}
|
||||
|
||||
// only Admin
|
||||
const toExport = async () => {
|
||||
if (api_type !== 'admin') {
|
||||
return false
|
||||
}
|
||||
const q = { ...listQuery }
|
||||
q.page_size = 1000000
|
||||
q.page = 1
|
||||
const res = await admin_list(q).catch(_ => false)
|
||||
if (res) {
|
||||
const csv = jsonToCsv(res.data.list)
|
||||
downBlob(csv, 'loginLog.csv')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
listRes,
|
||||
listQuery,
|
||||
@@ -91,5 +117,6 @@ export function useRepositories () {
|
||||
handlerQuery,
|
||||
del,
|
||||
batchdel,
|
||||
toExport,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handlerQuery">{{ T('Filter')}}</el-button>
|
||||
<el-button type="primary" @click="handlerQuery">{{ T('Filter') }}</el-button>
|
||||
<el-button type="danger" @click="toBatchDelete">{{ T('BatchDelete') }}</el-button>
|
||||
<el-button type="success" @click="toExport">{{ T('Export') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
@@ -28,15 +29,19 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="client" label="client" align="center" width="120"/>
|
||||
<el-table-column prop="peer.id" :label="T('Peer')" align="center"/>
|
||||
<el-table-column prop="peer.id" :label="T('Peer')" align="center">
|
||||
<template #default="{row}">
|
||||
{{ row.device_id ? row.device_id : peer?.id }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uuid" label="uuid" align="center"/>
|
||||
<el-table-column prop="ip" label="ip" align="center" width="150"/>
|
||||
<el-table-column prop="type" label="type" align="center" width="100"/>
|
||||
<el-table-column prop="platform" label="platform" align="center" width="120" show-overflow-tooltip/>
|
||||
<el-table-column prop="platform" label="Platform/UA" align="center" width="120" show-overflow-tooltip/>
|
||||
<el-table-column prop="created_at" :label="T('CreatedAt')" align="center"/>
|
||||
<el-table-column :label="T('Actions')" align="center" width="400">
|
||||
<template #default="{row}">
|
||||
<el-button type="danger" @click="del(row)">{{T('Delete')}}</el-button>
|
||||
<el-button type="danger" @click="del(row)">{{ T('Delete') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -58,6 +63,8 @@
|
||||
import { loadAllUsers } from '@/global'
|
||||
import { useRepositories } from '@/views/login/log.js'
|
||||
import { T } from '@/utils/i18n'
|
||||
import { list } from '@/api/peer'
|
||||
import { downBlob, jsonToCsv } from '@/utils/file'
|
||||
|
||||
const { allUsers, getAllUsers } = loadAllUsers()
|
||||
getAllUsers()
|
||||
@@ -69,7 +76,8 @@
|
||||
handlerQuery,
|
||||
del,
|
||||
batchdel,
|
||||
} = useRepositories()
|
||||
toExport,
|
||||
} = useRepositories('admin')
|
||||
|
||||
onMounted(getList)
|
||||
onActivated(getList)
|
||||
@@ -87,6 +95,7 @@
|
||||
}
|
||||
batchdel(multipleSelection.value)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<div class="login-card">
|
||||
<img src="@/assets/logo.png" alt="logo" class="login-logo"/>
|
||||
|
||||
<el-form label-position="top" class="login-form">
|
||||
<el-form v-if="!disablePwd" label-position="top" class="login-form">
|
||||
<el-form-item :label="T('Username')">
|
||||
<el-input v-model="form.username" class="login-input"></el-input>
|
||||
<el-input v-model="form.username" type="username" class="login-input"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="T('Password')">
|
||||
@@ -25,7 +25,7 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="divider" v-if="options.length > 0">
|
||||
<div class="divider" v-if="options.length > 0 && !disablePwd">
|
||||
<span>{{ T('or login in with') }}</span>
|
||||
</div>
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
password: '',
|
||||
platform: platform,
|
||||
captcha: '',
|
||||
captcha_id: ''
|
||||
})
|
||||
|
||||
const captchaCode = ref('')
|
||||
@@ -87,6 +88,7 @@
|
||||
if (!res.code) {
|
||||
ElMessage.success(T('LoginSuccess'))
|
||||
router.push({ path: redirect || '/', replace: true })
|
||||
return
|
||||
}
|
||||
if (res.code === 110) {
|
||||
// need captcha
|
||||
@@ -98,6 +100,7 @@
|
||||
const captchaRes = await captcha().catch(_ => false)
|
||||
console.log(captchaRes)
|
||||
captchaCode.value = captchaRes.data.captcha
|
||||
form.captcha_id = captchaRes.data.captcha.id
|
||||
}
|
||||
|
||||
const handleOIDCLogin = (provider) => {
|
||||
@@ -123,11 +126,17 @@
|
||||
}
|
||||
|
||||
const allowRegister = ref(false)
|
||||
const disablePwd = ref(false)
|
||||
const loadLoginOptions = async () => {
|
||||
try {
|
||||
const res = await loginOptions().catch(_ => false)
|
||||
if (!res || !res.data) return console.error('No valid response received')
|
||||
res.data.ops.map(option => (options.push({ name: option }))) // 创建新的对象数组
|
||||
if (res.data.auto_oidc) {
|
||||
// 如果有自动OIDC登录选项,直接调用第一个
|
||||
handleOIDCLogin(res.data.ops[0])
|
||||
}
|
||||
disablePwd.value = res.data.disable_pwd
|
||||
allowRegister.value = res.data.register
|
||||
if (res.data.need_captcha) {
|
||||
loadCaptcha()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card :title="T('Userinfo')" shadow="hover">
|
||||
<el-form class="info-form" ref="form" label-width="120px" label-suffix=":" >
|
||||
<el-form class="info-form" ref="form" label-width="120px" label-suffix=":">
|
||||
<el-form-item :label="T('Username')">
|
||||
<div>{{ userStore.username }}</div>
|
||||
</el-form-item>
|
||||
@@ -31,7 +31,7 @@
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card shadow="hover" style="margin-top: 20px">
|
||||
<div v-html="appStore.setting.hello"></div>
|
||||
<div v-html="html"></div>
|
||||
</el-card>
|
||||
<changePwdDialog v-model:visible="changePwdVisible"></changePwdDialog>
|
||||
</div>
|
||||
@@ -39,13 +39,14 @@
|
||||
|
||||
<script setup>
|
||||
import changePwdDialog from '@/components/changePwdDialog.vue'
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { useAppStore } from '@/store/app'
|
||||
import { bind, unbind } from '@/api/oauth'
|
||||
import { myOauth } from '@/api/user'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { T } from '@/utils/i18n'
|
||||
import { marked } from 'marked'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
@@ -63,7 +64,7 @@
|
||||
}
|
||||
getMyOauth()
|
||||
const toBind = async (row) => {
|
||||
const res = await bind({ op: row.op}).catch(_ => false)
|
||||
const res = await bind({ op: row.op }).catch(_ => false)
|
||||
if (res) {
|
||||
const { code, url } = res.data
|
||||
window.open(url)
|
||||
@@ -84,6 +85,9 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const html = computed(_ => marked(appStore.setting.hello||''))
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="list-query" shadow="hover">
|
||||
<el-form inline label-width="80px">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handlerQuery">{{ T('Filter') }}</el-button>
|
||||
<el-button type="danger" @click="toBatchDelete">{{ T('BatchDelete') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="list-body" shadow="hover">
|
||||
<el-table :data="listRes.list" v-loading="listRes.loading" border @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" align="center" width="50"/>
|
||||
<el-table-column prop="client" label="client" align="center" width="120"/>
|
||||
<el-table-column prop="peer.id" :label="T('Peer')" align="center">
|
||||
<template #default="{row}">
|
||||
{{ row.device_id ? row.device_id : peer?.id }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uuid" label="uuid" align="center"/>
|
||||
<el-table-column prop="ip" label="ip" align="center" width="150"/>
|
||||
<el-table-column prop="type" label="type" align="center" width="100"/>
|
||||
<el-table-column prop="platform" label="Platform/UA" align="center" width="120" show-overflow-tooltip/>
|
||||
<el-table-column prop="created_at" :label="T('CreatedAt')" align="center"/>
|
||||
<el-table-column :label="T('Actions')" align="center" width="400">
|
||||
<template #default="{row}">
|
||||
<el-button type="danger" @click="del(row)">{{ T('Delete') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<el-card class="list-page" shadow="hover">
|
||||
<el-pagination background
|
||||
layout="prev, pager, next, sizes, jumper"
|
||||
:page-sizes="[10,20,50,100]"
|
||||
v-model:page-size="listQuery.page_size"
|
||||
v-model:current-page="listQuery.page"
|
||||
:total="listRes.total">
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onActivated, onMounted, ref, watch } from 'vue'
|
||||
import { useRepositories } from '@/views/login/log.js'
|
||||
import { T } from '@/utils/i18n'
|
||||
|
||||
const {
|
||||
listRes,
|
||||
listQuery,
|
||||
getList,
|
||||
handlerQuery,
|
||||
del,
|
||||
batchdel,
|
||||
} = useRepositories('my')
|
||||
|
||||
onMounted(getList)
|
||||
onActivated(getList)
|
||||
|
||||
watch(() => listQuery.page, getList)
|
||||
|
||||
watch(() => listQuery.page_size, handlerQuery)
|
||||
const multipleSelection = ref([])
|
||||
const handleSelectionChange = (val) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
const toBatchDelete = () => {
|
||||
if (multipleSelection.value.length === 0) {
|
||||
return
|
||||
}
|
||||
batchdel(multipleSelection.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.list-query .el-select {
|
||||
--el-select-width: 160px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -51,6 +51,7 @@
|
||||
<el-table-column prop="username" :label="T('Username')" align="center" width="120"/>
|
||||
<el-table-column prop="uuid" :label="T('Uuid')" align="center" width="120" show-overflow-tooltip/>
|
||||
<el-table-column prop="version" :label="T('Version')" align="center" width="80"/>
|
||||
<el-table-column prop="alias" :label="T('Alias')" align="center" width="80"/>
|
||||
<el-table-column prop="created_at" :label="T('CreatedAt')" align="center" width="150"/>
|
||||
<el-table-column prop="updated_at" :label="T('UpdatedAt')" align="center" width="150"/>
|
||||
<el-table-column :label="T('Actions')" align="center" width="500" class-name="table-actions" fixed="right">
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<el-table-column prop="op" :label="T('IdP')" align="center"/>
|
||||
<el-table-column prop="oauth_type" :label="T('Type')" align="center"/>
|
||||
<el-table-column prop="auto_register" :label="T('AutoRegister')" align="center"/>
|
||||
<el-table-column prop="pkce_enable" :label="T('PkceEnable')" align="center"/>
|
||||
<el-table-column prop="pkce_method" :label="T('PkceMethod')" align="center"/>
|
||||
<el-table-column prop="created_at" :label="T('CreatedAt')" align="center"/>
|
||||
<el-table-column prop="updated_at" :label="T('UpdatedAt')" align="center"/>
|
||||
<el-table-column :label="T('Actions')" align="center">
|
||||
@@ -35,7 +37,7 @@
|
||||
</el-card>
|
||||
<el-dialog v-model="formVisible" :title="!formData.id?T('Create') :T('Update')" width="800">
|
||||
<el-form class="dialog-form" ref="form" :model="formData" :rules="rules" label-width="120px">
|
||||
<el-form-item label="Type" prop="">
|
||||
<el-form-item label="Type" prop="oauth_type">
|
||||
<el-radio-group v-model="formData.oauth_type" :disabled="!!formData.id">
|
||||
<el-radio v-for="item in types" :key="item.value" :value="item.value" style="display: block">
|
||||
{{ item.label }}
|
||||
@@ -49,16 +51,38 @@
|
||||
<el-input v-model="formData.issuer" :placeholder="`${T('Check your IdP docs, without')} '/.well-known/openid-configuration'`"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-show="formData.oauth_type === 'oidc'" label="Scopes" prop="scopes">
|
||||
<el-input v-model="formData.scopes" :placeholder="`${T('Optional, default is')} 'openid,profile,email'`" ></el-input>
|
||||
<el-input v-model="formData.scopes" :placeholder="`${T('Optional, default is')} 'openid,profile,email'`"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="ClientId" prop="client_id">
|
||||
<el-input v-model="formData.client_id"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="ClientSecret" prop="client_secret">
|
||||
<el-input v-model="formData.client_secret"></el-input>
|
||||
<el-input
|
||||
v-model="formData.client_secret"
|
||||
:type="formData.id ? 'password' : 'text'"
|
||||
:show-password="!formData.id"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="RedirectUrl" prop="redirect_url">
|
||||
<el-input v-model="formData.redirect_url"></el-input>
|
||||
<div @click="copyRedirectUrl">{{ defaultRedirect() }}
|
||||
<el-icon>
|
||||
<CopyDocument></CopyDocument>
|
||||
</el-icon>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="PkceEnable" prop="pkce_enable">
|
||||
<el-switch v-model="formData.pkce_enable"
|
||||
:active-value="true"
|
||||
:inactive-value="false">
|
||||
</el-switch>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="formData.pkce_enable" label="PkceMethod" prop="pkce_method">
|
||||
<el-select v-model="formData.pkce_method" placeholder="Select PKCE Method">
|
||||
<el-option label="S256 (Recommended)" value="S256"></el-option>
|
||||
<el-option label="Plain" value="plain"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="T('AutoRegister')" prop="auto_register">
|
||||
<el-switch v-model="formData.auto_register"
|
||||
@@ -81,6 +105,15 @@
|
||||
import { list, create, update, detail, remove } from '@/api/oauth'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { T } from '@/utils/i18n'
|
||||
import { handleClipboard } from '@/utils/clipboard'
|
||||
import { useAppStore } from '@/store/app'
|
||||
import { CopyDocument } from '@element-plus/icons'
|
||||
|
||||
const app = useAppStore()
|
||||
|
||||
const copyRedirectUrl = (e) => {
|
||||
handleClipboard(defaultRedirect(), e)
|
||||
}
|
||||
|
||||
const listRes = reactive({
|
||||
list: [], total: 0, loading: false,
|
||||
@@ -92,7 +125,8 @@
|
||||
const types = [
|
||||
{ value: 'github', label: 'GitHub' },
|
||||
{ value: 'google', label: 'Google' },
|
||||
{ value: 'oidc', label: 'OIDC' }
|
||||
{ value: 'linuxdo', label: 'LinuxDo' },
|
||||
{ value: 'oidc', label: 'OIDC' },
|
||||
]
|
||||
const getList = async () => {
|
||||
listRes.loading = true
|
||||
@@ -145,14 +179,35 @@
|
||||
redirect_url: '',
|
||||
scopes: '',
|
||||
auto_register: false,
|
||||
pkce_enable: false,
|
||||
pkce_method: 'S256',
|
||||
})
|
||||
const rules = {
|
||||
client_id: [{ required: true, message: T('ParamRequired', { param: 'client_id' }), trigger: 'blur' }],
|
||||
client_secret: [{ required: true, message: T('ParamRequired', { param: 'client_secret' }), trigger: 'blur' }],
|
||||
redirect_url: [{ required: true, message: T('ParamRequired', { param: 'redirect_url' }), trigger: 'blur' }],
|
||||
// redirect_url: [{ required: true, message: T('ParamRequired', { param: 'redirect_url' }), trigger: 'blur' }],
|
||||
oauth_type: [{ required: true, message: T('ParamRequired', { param: 'oauth_type' }), trigger: 'blur' }],
|
||||
issuer: [{ required: true, message: T('ParamRequired', { param: 'issuer' }), trigger: 'blur' }],
|
||||
pkce_method: [
|
||||
{ required: false, message: T('ParamRequired', { param: 'pkce_method' }), trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
const allowedValues = ['S256', 'plain']
|
||||
if (!allowedValues.includes(value)) {
|
||||
callback(new Error(T('InvalidParam', { param: 'pkce_method' })))
|
||||
} else {
|
||||
callback() // 校验通过
|
||||
}
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const defaultRedirect = () => {
|
||||
return `${app.setting.rustdeskConfig.api_server || window.location.origin}/api/oidc/callback`
|
||||
}
|
||||
|
||||
const toEdit = (row) => {
|
||||
formVisible.value = true
|
||||
formData.id = row.id
|
||||
@@ -161,10 +216,11 @@
|
||||
formData.issuer = row.issuer
|
||||
formData.client_id = row.client_id
|
||||
formData.client_secret = row.client_secret
|
||||
formData.redirect_url = row.redirect_url
|
||||
// formData.redirect_url = row.redirect_url || defaultRedirect()
|
||||
formData.scopes = row.scopes
|
||||
formData.auto_register = row.auto_register
|
||||
|
||||
formData.pkce_enable = row.pkce_enable
|
||||
formData.pkce_method = row.pkce_method
|
||||
}
|
||||
const toAdd = () => {
|
||||
formVisible.value = true
|
||||
@@ -174,9 +230,11 @@
|
||||
formData.issuer = ''
|
||||
formData.client_id = ''
|
||||
formData.client_secret = ''
|
||||
formData.redirect_url = ''
|
||||
// formData.redirect_url = defaultRedirect()
|
||||
formData.scopes = ''
|
||||
formData.auto_register = false
|
||||
formData.pkce_enable = false
|
||||
formData.pkce_method = 'S256'
|
||||
}
|
||||
const form = ref(null)
|
||||
const submit = async () => {
|
||||
|
||||
+212
-16
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="list-query" shadow="hover">
|
||||
<el-form inline label-width="150px">
|
||||
<el-form inline label-width="60px">
|
||||
<el-form-item label="ID">
|
||||
<el-input v-model="listQuery.id" clearable/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="T('Hostname')">
|
||||
<el-input v-model="listQuery.hostname" clearable/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="T('LastOnlineTime')">
|
||||
<el-form-item :label="T('LastOnlineTime')" label-width="100px">
|
||||
<el-select v-model="listQuery.time_ago" clearable>
|
||||
<el-option
|
||||
v-for="item in timeFilters"
|
||||
@@ -19,40 +19,87 @@
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="T('Username')">
|
||||
<el-input v-model="listQuery.username" clearable/>
|
||||
</el-form-item>
|
||||
<el-form-item label="IP">
|
||||
<el-input v-model="listQuery.ip" clearable/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handlerQuery">{{ T('Filter') }}</el-button>
|
||||
<el-button type="danger" @click="toAdd">{{ T('Add') }}</el-button>
|
||||
<el-button type="success" @click="toExport">{{ T('Export') }}</el-button>
|
||||
<el-popover :visible="showImport" placement="bottom" :width="600">
|
||||
<el-upload
|
||||
class="upload-demo"
|
||||
drag
|
||||
accept=".csv"
|
||||
:before-upload="parseCsv"
|
||||
>
|
||||
<el-icon class="el-icon--upload">
|
||||
<upload-filled/>
|
||||
</el-icon>
|
||||
<div class="el-upload__text">
|
||||
{{ T('Drop file here or click to upload') }}
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
{{ T('Please upload csv file') }} <br>
|
||||
{{ T('Columns') }}: <span style="font-weight: bold;font-size: 15px">id,cpu,hostname,memory,os,username,uuid,version,group_id</span>
|
||||
<br>
|
||||
<span>{{ T('You can reference export file') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<el-button @click="showImport=false" type="primary">{{ T('Cancel') }}</el-button>
|
||||
<template #reference>
|
||||
<el-button @click="showImport=true" type="danger" :icon="ArrowDown">{{ T('Import') }}</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
<el-button type="danger" @click="toBatchDelete">{{ T('BatchDelete') }}</el-button>
|
||||
<el-button type="primary" @click="toBatchAddToAB">{{ T('BatchAddToAB') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="list-body" shadow="hover">
|
||||
<div style="text-align: right; margin-bottom: 10px">
|
||||
<el-button :icon="Setting" @click="showColumnSetting"></el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="listRes.list" v-loading="listRes.loading" border size="small" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center"/>
|
||||
<el-table-column prop="id" label="ID" align="center" width="150">
|
||||
<template v-for="c in visibleColumns.filter(cc => cc.visible)" :key="c">
|
||||
<el-table-column v-if="c.name==='id'" prop="id" label="ID" align="center" width="150">
|
||||
<template #default="{row}">
|
||||
<span>{{ row.id }} <el-icon @click="handleClipboard(row.id, $event)"><CopyDocument/></el-icon></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="cpu" label="CPU" align="center" width="100" show-overflow-tooltip/>
|
||||
<el-table-column prop="hostname" :label="T('Hostname')" align="center" width="120"/>
|
||||
<el-table-column prop="memory" :label="T('Memory')" align="center" width="120"/>
|
||||
<el-table-column prop="os" :label="T('Os')" align="center" width="120" show-overflow-tooltip/>
|
||||
<el-table-column prop="last_online_time" :label="T('LastOnlineTime')" align="center" min-width="120">
|
||||
<el-table-column v-if="c.name==='cpu'" prop="cpu" label="CPU" align="center" width="100" show-overflow-tooltip/>
|
||||
<el-table-column v-if="c.name==='hostname'" prop="hostname" :label="T('Hostname')" align="center" width="120"/>
|
||||
<el-table-column v-if="c.name==='memory'" prop="memory" :label="T('Memory')" align="center" width="120"/>
|
||||
<el-table-column v-if="c.name==='os'" prop="os" :label="T('Os')" align="center" width="120" show-overflow-tooltip/>
|
||||
<el-table-column v-if="c.name==='last_online_time'" prop="last_online_time" :label="T('LastOnlineTime')" align="center" min-width="120">
|
||||
<template #default="{row}">
|
||||
<div class="last_oline_time">
|
||||
<span> {{ row.last_online_time ? timeAgo(row.last_online_time * 1000) : '-' }}</span> <span class="dot" :class="{red: timeDis(row.last_online_time) >= 60, green: timeDis(row.last_online_time)< 60}"></span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="last_online_ip" :label="T('LastOnlineIp')" align="center" min-width="120"/>
|
||||
<el-table-column prop="username" :label="T('Username')" align="center" width="120"/>
|
||||
<el-table-column prop="uuid" :label="T('Uuid')" align="center" width="120" show-overflow-tooltip/>
|
||||
<el-table-column prop="version" :label="T('Version')" align="center" width="80"/>
|
||||
<el-table-column prop="created_at" :label="T('CreatedAt')" align="center" width="150"/>
|
||||
<el-table-column prop="updated_at" :label="T('UpdatedAt')" align="center" width="150"/>
|
||||
<el-table-column v-if="c.name==='last_online_ip'" prop="last_online_ip" :label="T('LastOnlineIp')" align="center" min-width="120"/>
|
||||
<el-table-column v-if="c.name==='username'" prop="username" :label="T('Username')" align="center" width="120"/>
|
||||
<el-table-column v-if="c.name==='group_id'" prop="group_id" :label="T('Group')" align="center" width="120">
|
||||
<template #default="{row}">
|
||||
<span v-if="row.group_id"> <el-tag>{{ groupListRes.list?.find(g => g.id === row.group_id)?.name }} </el-tag> </span>
|
||||
<span v-else> - </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="c.name==='uuid'" prop="uuid" :label="T('Uuid')" align="center" width="120" show-overflow-tooltip/>
|
||||
<el-table-column v-if="c.name==='version'" prop="version" :label="T('Version')" align="center" width="80"/>
|
||||
<el-table-column v-if="c.name==='alias'" prop="alias" :label="T('Alias')" align="center" width="80"/>
|
||||
<el-table-column v-if="c.name==='created_at'" prop="created_at" :label="T('CreatedAt')" align="center" width="150"/>
|
||||
<el-table-column v-if="c.name==='updated_at'" prop="updated_at" :label="T('UpdatedAt')" align="center" width="150"/>
|
||||
</template>
|
||||
|
||||
<el-table-column :label="T('Actions')" align="center" width="500" class-name="table-actions" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button type="success" @click="connectByClient(row.id)">{{ T('Link') }}</el-button>
|
||||
@@ -78,6 +125,16 @@
|
||||
<el-form-item label="ID" prop="id" required>
|
||||
<el-input v-model="formData.id"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="T('Group')" prop="group_id">
|
||||
<el-select v-model="formData.group_id">
|
||||
<el-option
|
||||
v-for="item in groupListRes.list"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="T('Username')" prop="username">
|
||||
<el-input v-model="formData.username"></el-input>
|
||||
</el-form-item>
|
||||
@@ -99,7 +156,9 @@
|
||||
<el-form-item :label="T('Version')" prop="version">
|
||||
<el-input v-model="formData.version"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="T('Alias')" prop="alias">
|
||||
<el-input v-model="formData.alias"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="formVisible = false">{{ T('Cancel') }}</el-button>
|
||||
<el-button @click="submit" type="primary">{{ T('Submit') }}</el-button>
|
||||
@@ -145,12 +204,35 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="columnSettingVisible" title="Column Setting">
|
||||
<div v-for="(row, key) in visibleColumns" :key="key" style="margin-bottom: 10px;display: flex;align-items: center">
|
||||
<div style="width: 200px">
|
||||
<el-checkbox v-model="row.visible" :label="true">{{ T(row.label) }}</el-checkbox>
|
||||
</div>
|
||||
<div @click="upColumn(key)" style="width: 100px;cursor: pointer">
|
||||
<el-icon :size="20">
|
||||
<ArrowUp/>
|
||||
</el-icon>
|
||||
</div>
|
||||
<div @click="downColumn(key)" style="width: 100px;cursor: pointer">
|
||||
<el-icon :size="20">
|
||||
<ArrowDown/>
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="columnSettingVisible = false">{{ T('Cancel') }}</el-button>
|
||||
<el-button type="primary" @click="saveColumnSetting">{{ T('Save') }}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { batchRemove, create, list, remove, update } from '@/api/peer'
|
||||
import { list as groupList } from '@/api/device_group'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { toWebClientLink } from '@/utils/webclient'
|
||||
import { T } from '@/utils/i18n'
|
||||
@@ -159,13 +241,35 @@
|
||||
import { loadAllUsers } from '@/global'
|
||||
import { useAppStore } from '@/store/app'
|
||||
import { connectByClient } from '@/utils/peer'
|
||||
import { CopyDocument } from '@element-plus/icons'
|
||||
import { ArrowDown, ArrowUp, CopyDocument, Setting } from '@element-plus/icons'
|
||||
import { handleClipboard } from '@/utils/clipboard'
|
||||
import { batchCreateFromPeers } from '@/api/address_book'
|
||||
import { useRepositories as useCollectionRepositories } from '@/views/address_book/collection'
|
||||
import createABForm from '@/views/peer/createABForm.vue'
|
||||
import { UploadFilled } from '@element-plus/icons-vue'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
//group
|
||||
const groupListRes = reactive({
|
||||
list: [], total: 0, loading: false,
|
||||
})
|
||||
const groupListQuery = reactive({
|
||||
page: 1,
|
||||
page_size: 999,
|
||||
})
|
||||
const getGroupList = async () => {
|
||||
groupListRes.loading = true
|
||||
const res = await groupList(groupListQuery).catch(_ => false)
|
||||
groupListRes.loading = false
|
||||
if (res) {
|
||||
groupListRes.list = res.data.list
|
||||
groupListRes.total = res.data.total
|
||||
}
|
||||
}
|
||||
onMounted(getGroupList)
|
||||
//
|
||||
|
||||
const listRes = reactive({
|
||||
list: [], total: 0, loading: false,
|
||||
})
|
||||
@@ -175,6 +279,8 @@
|
||||
time_ago: null,
|
||||
id: '',
|
||||
hostname: '',
|
||||
username: '',
|
||||
ip: '',
|
||||
})
|
||||
|
||||
const getList = async () => {
|
||||
@@ -220,6 +326,7 @@
|
||||
const formVisible = ref(false)
|
||||
const formData = reactive({
|
||||
row_id: 0,
|
||||
group_id: null,
|
||||
cpu: '',
|
||||
hostname: '',
|
||||
id: '',
|
||||
@@ -295,6 +402,54 @@
|
||||
}
|
||||
}
|
||||
|
||||
const showImport = ref(false)
|
||||
const canKeys = ['id', 'cpu', 'hostname', 'memory', 'os', 'username', 'uuid', 'version', 'group_id']
|
||||
const parseCsv = (file) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = async (e) => {
|
||||
const data = e.target.result
|
||||
console.log(data)
|
||||
//组装数据
|
||||
const rows = data.split('\n')
|
||||
const keys = rows[0].split(',')
|
||||
console.log(keys, rows.slice(1).map(row => row.split(',')))
|
||||
const values = rows.slice(1).map(row => {
|
||||
const obj = {}
|
||||
row.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/).forEach((v, i) => {
|
||||
//去掉两边的"
|
||||
obj[keys[i]] = v.trim().replace(/^"|"$/g, '')
|
||||
})
|
||||
return obj
|
||||
}).filter(item => item.id)
|
||||
// console.log(values)
|
||||
//移除不需要的key
|
||||
values.forEach(item => {
|
||||
item.group_id = parseInt(item.group_id)
|
||||
Object.keys(item).forEach(key => {
|
||||
if (!canKeys.includes(key)) {
|
||||
delete item[key]
|
||||
}
|
||||
})
|
||||
})
|
||||
console.log(values)
|
||||
const pa = []
|
||||
values.map(item => {
|
||||
pa.push(create(item))
|
||||
})
|
||||
const res = await Promise.all(pa).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
getList()
|
||||
}
|
||||
|
||||
}
|
||||
reader.readAsText(file)
|
||||
return false
|
||||
}
|
||||
const toImport = () => {
|
||||
ElMessage.warning('暂未实现')
|
||||
}
|
||||
|
||||
const ABFormVisible = ref(false)
|
||||
const clickRow = ref({})
|
||||
const toAddressBook = (row) => {
|
||||
@@ -370,6 +525,47 @@
|
||||
}
|
||||
// 批量添加到地址簿 end
|
||||
|
||||
const columnSettingVisible = ref(false)
|
||||
const allColumns = ref([
|
||||
{ name: 'id', visible: true, label: 'Id' },
|
||||
{ name: 'cpu', visible: true, label: 'Cpu' },
|
||||
{ name: 'hostname', visible: true, label: 'Hostname' },
|
||||
{ name: 'memory', visible: true, label: 'Memory' },
|
||||
{ name: 'os', visible: true, label: 'Os' },
|
||||
{ name: 'last_online_time', visible: true, label: 'LastOnlineTime' },
|
||||
{ name: 'last_online_ip', visible: true, label: 'LastOnlineIp' },
|
||||
{ name: 'username', visible: true, label: 'Username' },
|
||||
{ name: 'group_id', visible: true, label: 'Group' },
|
||||
{ name: 'uuid', visible: true, label: 'Uuid' },
|
||||
{ name: 'version', visible: true, label: 'Version' },
|
||||
{ name: 'alias', visible: true, label: 'Alias' },
|
||||
{ name: 'created_at', visible: true, label: 'CreatedAt' },
|
||||
{ name: 'updated_at', visible: true, label: 'UpdatedAt' },
|
||||
])
|
||||
const visibleColumns = ref(JSON.parse(localStorage.getItem('peer_visible_columns')) || allColumns.value)
|
||||
const showColumnSetting = () => {
|
||||
columnSettingVisible.value = true
|
||||
}
|
||||
const saveColumnSetting = () => {
|
||||
localStorage.setItem('peer_visible_columns', JSON.stringify(visibleColumns.value))
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
columnSettingVisible.value = false
|
||||
}
|
||||
|
||||
const upColumn = (index) => {
|
||||
if (index === 0) return
|
||||
const col = visibleColumns.value[index]
|
||||
visibleColumns.value.splice(index, 1)
|
||||
visibleColumns.value.splice(index - 1, 0, col)
|
||||
|
||||
}
|
||||
const downColumn = (index) => {
|
||||
if (index === visibleColumns.value.length - 1) return
|
||||
const col = visibleColumns.value[index]
|
||||
visibleColumns.value.splice(index, 1)
|
||||
visibleColumns.value.splice(index + 1, 0, col)
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { register } from '@/api/user'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { useAppStore } from '@/store/app'
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
@@ -78,6 +79,7 @@
|
||||
return
|
||||
}
|
||||
userStore.saveUserData(res.data)
|
||||
useAppStore().loadConfig()
|
||||
ElMessage.success('Submit')
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<el-card class="simple-card" shadow="hover" v-loading="form.loading">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>ALWAYS_USE_RELAY</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :disabled="!canSend">
|
||||
<el-form-item>
|
||||
<el-switch v-model="form.option" active-value="Y" inactive-value="N"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="get">{{ T('Refresh') }}</el-button>
|
||||
<el-button @click="save" type="primary">{{ T('Save') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</template>
|
||||
<script setup>
|
||||
|
||||
import { T } from '@/utils/i18n'
|
||||
import { reactive, watch } from 'vue'
|
||||
import { sendCmd } from '@/api/rustdesk'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ID_TARGET } from '@/views/rustdesk/options'
|
||||
|
||||
const emits = defineEmits('success')
|
||||
const props = defineProps({
|
||||
canSend: Boolean,
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
cmd: 'aur',
|
||||
option: '',
|
||||
target: ID_TARGET,
|
||||
value: 0,
|
||||
loading: false,
|
||||
})
|
||||
const get = async () => {
|
||||
form.loading = true
|
||||
const res = await sendCmd({ cmd: 'aur', target: ID_TARGET }).catch(_ => false)
|
||||
form.loading = false
|
||||
if (res) {
|
||||
if (res.data === 'ALWAYS_USE_RELAY: true' || res.data === 'ALWAYS_USE_RELAY: true\n') {
|
||||
form.option = 'Y'
|
||||
} else {
|
||||
form.option = 'N'
|
||||
}
|
||||
}
|
||||
}
|
||||
const save = async () => {
|
||||
const res = await sendCmd(form).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
emits('success')
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.canSend, (v) => {
|
||||
if (v) {
|
||||
get()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<el-card class="simple-card" shadow="hover" v-loading="form.loading">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>BLACK_LIST</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :disabled="!canSend">
|
||||
<el-form-item>
|
||||
<el-input type="textarea" :model-value="form.list.join('|')" :rows="5"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getList">{{ T('Refresh') }}</el-button>
|
||||
<el-button @click="showForm('add')" type="primary">{{ T('Add') }}</el-button>
|
||||
<el-button @click="showForm('delete')" type="danger">{{ T('Delete') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-dialog v-model="form.form_visible" :title="form.form_type">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="IP">
|
||||
<el-input v-model="form.form_input"></el-input>
|
||||
<div>多个IP以 | 分割</div>
|
||||
<div v-if="form.form_type==='delete'">, 全部填 <strong>all</strong></div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="form.form_visible=false">{{ T('Cancel') }}</el-button>
|
||||
<el-button @click="form.form_type === 'add' ? add() : remove()" type="primary">{{ T('Submit') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</el-card>
|
||||
</template>
|
||||
<script setup>
|
||||
|
||||
import { T } from '@/utils/i18n'
|
||||
import { reactive, watch } from 'vue'
|
||||
import { sendCmd } from '@/api/rustdesk'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { RELAY_TARGET } from '@/views/rustdesk/options'
|
||||
|
||||
const props = defineProps({
|
||||
canSend: Boolean,
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
get_cmd: 'blacklist',
|
||||
add_cmd: 'blacklist-add',
|
||||
remove_cmd: 'blacklist-remove',
|
||||
list: [],
|
||||
target: RELAY_TARGET,
|
||||
loading: false,
|
||||
form_visible: false,
|
||||
form_input: '',
|
||||
form_type: '',
|
||||
})
|
||||
const getList = async () => {
|
||||
form.loading = true
|
||||
const res = await sendCmd({ cmd: form.get_cmd, target: RELAY_TARGET }).catch(_ => false)
|
||||
form.loading = false
|
||||
if (res) {
|
||||
form.list = res.data.split('\n').filter(i => i)
|
||||
}
|
||||
}
|
||||
const showForm = (type) => {
|
||||
form.form_visible = true
|
||||
form.form_input = ''
|
||||
form.form_type = type
|
||||
}
|
||||
const add = async () => {
|
||||
const res = await sendCmd({ cmd: form.add_cmd, option: form.form_input, target: RELAY_TARGET }).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
getList()
|
||||
}
|
||||
}
|
||||
const remove = async () => {
|
||||
const res = await sendCmd({ cmd: form.remove_cmd, option: form.form_input, target: RELAY_TARGET }).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
getList()
|
||||
}
|
||||
}
|
||||
watch(() => props.canSend, (v) => {
|
||||
if (v) {
|
||||
getList()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<el-card class="simple-card" shadow="hover" v-loading="form.loading">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>BLOCK_LIST</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :disabled="!canSend">
|
||||
<el-form-item>
|
||||
<el-input type="textarea" :model-value="form.list.join('|')" :rows="5"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getList">{{ T('Refresh') }}</el-button>
|
||||
<el-button @click="showForm('add')" type="primary">{{ T('Add') }}</el-button>
|
||||
<el-button @click="showForm('delete')" type="danger">{{ T('Delete') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-dialog v-model="form.form_visible" :title="form.form_type">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="IP">
|
||||
<el-input v-model="form.form_input"></el-input>
|
||||
<div>多个IP以 | 分割</div>
|
||||
<div v-if="form.form_type==='delete'">, 全部填 <strong>all</strong></div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="form.form_visible=false">{{ T('Cancel') }}</el-button>
|
||||
<el-button @click="form.form_type === 'add' ? add() : remove()" type="primary">{{ T('Submit') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</el-card>
|
||||
</template>
|
||||
<script setup>
|
||||
|
||||
import { T } from '@/utils/i18n'
|
||||
import { reactive, watch } from 'vue'
|
||||
import { sendCmd } from '@/api/rustdesk'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { RELAY_TARGET } from '@/views/rustdesk/options'
|
||||
|
||||
const props = defineProps({
|
||||
canSend: Boolean,
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
get_cmd: 'blocklist',
|
||||
add_cmd: 'blocklist-add',
|
||||
remove_cmd: 'blocklist-remove',
|
||||
list: [],
|
||||
target: RELAY_TARGET,
|
||||
loading: false,
|
||||
form_visible: false,
|
||||
form_input: '',
|
||||
form_type: '',
|
||||
})
|
||||
const getList = async () => {
|
||||
form.loading = true
|
||||
const res = await sendCmd({ cmd: form.get_cmd, target: RELAY_TARGET }).catch(_ => false)
|
||||
form.loading = false
|
||||
if (res) {
|
||||
form.list = res.data.split('\n').filter(i => i)
|
||||
}
|
||||
}
|
||||
const showForm = (type) => {
|
||||
form.form_visible = true
|
||||
form.form_input = ''
|
||||
form.form_type = type
|
||||
}
|
||||
const add = async () => {
|
||||
const res = await sendCmd({ cmd: form.add_cmd, option: form.form_input, target: RELAY_TARGET }).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
getList()
|
||||
}
|
||||
}
|
||||
const remove = async () => {
|
||||
const res = await sendCmd({ cmd: form.remove_cmd, option: form.form_input, target: RELAY_TARGET }).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
getList()
|
||||
}
|
||||
}
|
||||
watch(() => props.canSend, (v) => {
|
||||
if (v) {
|
||||
getList()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,289 @@
|
||||
<template>
|
||||
<div>
|
||||
<h4 v-html="T('ServerCmdTips', {wiki: '<a target=\'_blank\' href=\'https://github.com/lejianwen/rustdesk-api/wiki/Rustdesk-Command\'>WIKI</a>'})"></h4>
|
||||
<h5>
|
||||
<span>ID {{ T('Status') }}: </span>
|
||||
<el-tag v-if="canSendIdServerCmd" type="success">{{ T('Available') }}</el-tag>
|
||||
<el-tag v-else type="danger">{{ T('NotAvailable') }}</el-tag>
|
||||
<el-button size="small" type="text" @click="refreshCanSendIdServerCmd">{{ T('Refresh') }}</el-button>
|
||||
</h5>
|
||||
<h5>
|
||||
<span>RELAY {{ T('Status') }}: </span>
|
||||
<el-tag v-if="canSendRelayServerCmd" type="success">{{ T('Available') }}</el-tag>
|
||||
<el-tag v-else type="danger">{{ T('NotAvailable') }}</el-tag>
|
||||
<el-button size="small" type="text" @click="refreshCanSendRelayServerCmd">{{ T('Refresh') }}</el-button>
|
||||
</h5>
|
||||
<el-tabs
|
||||
v-model="activeName"
|
||||
type="card"
|
||||
>
|
||||
<el-tab-pane :label="T('Simple')" name="Simple">
|
||||
<el-space wrap>
|
||||
<RelayServers ref="rs" :can-send="canSendIdServerCmd"/>
|
||||
<alwaysUseRelay :can-send="canSendIdServerCmd" @success="handleAlwaysUseRelaySuccess"/>
|
||||
<mustLogin :can-send="canControlMustLogin&&canSendIdServerCmd"/>
|
||||
<usage :can-send="canSendRelayServerCmd"/>
|
||||
<blocklist :can-send="canSendRelayServerCmd"/>
|
||||
<blacklist :can-send="canSendRelayServerCmd"/>
|
||||
</el-space>
|
||||
|
||||
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="T('Advanced')" name="Advanced">
|
||||
<el-card class="list-query" shadow="hover">
|
||||
<el-form inline label-width="80px">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handlerQuery">{{ T('Filter') }}</el-button>
|
||||
<el-button type="danger" @click="toAdd">{{ T('Add') }}</el-button>
|
||||
<el-button type="success" :disabled="!canSendIdServerCmd" @click="showCmd({cmd:'',option:'',target:ID_TARGET})">{{ T('Send') }} To Id</el-button>
|
||||
<el-button type="success" :disabled="!canSendRelayServerCmd" @click="showCmd({cmd:'',option:'',target:RELAY_TARGET})">{{ T('Send') }} To Relay</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="list-body" shadow="hover">
|
||||
<el-table :data="listRes.list" v-loading="listRes.loading" border>
|
||||
<el-table-column prop="cmd" label="cmd" align="center"></el-table-column>
|
||||
<el-table-column prop="alias" label="alias" align="center"></el-table-column>
|
||||
<el-table-column prop="option" label="option" align="center"></el-table-column>
|
||||
<el-table-column prop="explain" label="explain" align="center"></el-table-column>
|
||||
<el-table-column label="actions" align="center">
|
||||
<template #default="{row}">
|
||||
<el-button type="success" :disabled="!canSendCmd(row.target)" @click="showCmd(row)">{{ T('Send') }}</el-button>
|
||||
<el-button v-if="row.id" type="primary" @click="toUpdate(row)">{{ T('Edit') }}</el-button>
|
||||
<el-button v-if="row.id" type="danger" @click="del(row)">{{ T('Delete') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog v-model="formVisible">
|
||||
<el-form label-width="150">
|
||||
<el-form-item label="cmd">
|
||||
<el-input v-model="formData.cmd"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="alias">
|
||||
<el-input v-model="formData.alias"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="option">
|
||||
<el-input v-model="formData.option"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="target">
|
||||
<el-radio-group v-model="formData.target">
|
||||
<el-radio label="id_server" value="21115"></el-radio>
|
||||
<el-radio label="relay_server" value="21117"></el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="explain">
|
||||
<el-input v-model="formData.explain"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submit">{{ T('Submit') }}</el-button>
|
||||
<el-button @click="cancel">{{ T('Cancel') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog :title="T('SendCmd')" v-model="showCmdForm">
|
||||
<el-form label-width="150" :disabled="!canSendCmd(customCmd.target)">
|
||||
<el-form-item label="cmd">
|
||||
<el-input v-model="customCmd.cmd"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="option">
|
||||
<el-input v-model="customCmd.option"></el-input>
|
||||
<el-text v-if="customCmd.example.trim()" style="margin-top: 5px">Example:
|
||||
<el-text type="primary">{{ customCmd.example }}</el-text>
|
||||
</el-text>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="target">
|
||||
<el-radio-group v-model="customCmd.target">
|
||||
<el-radio label="id_server" value="21115"></el-radio>
|
||||
<el-radio label="relay_server" value="21117"></el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>-->
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitCmd">{{ T('Send') }}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item :label="T('Result')">
|
||||
<el-input type="textarea" readonly disabled v-model="customCmd.res" rows="15"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { create, list, remove, sendCmd, update } from '@/api/rustdesk'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { T } from '@/utils/i18n'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ID_TARGET, RELAY_TARGET } from '@/views/rustdesk/options'
|
||||
import blocklist from '@/views/rustdesk/blocklist.vue'
|
||||
import blacklist from '@/views/rustdesk/blacklist.vue'
|
||||
import alwaysUseRelay from '@/views/rustdesk/always_use_relay.vue'
|
||||
import RelayServers from '@/views/rustdesk/relay_servers.vue'
|
||||
import mustLogin from '@/views/rustdesk/must_login.vue'
|
||||
import usage from '@/views/rustdesk/usage.vue'
|
||||
|
||||
const activeName = ref('Simple')
|
||||
|
||||
const canSendIdServerCmd = ref(false)
|
||||
const checkCanSendIdServerCmd = async () => {
|
||||
const res = await sendCmd({ cmd: 'h', target: ID_TARGET }).catch(_ => false)
|
||||
canSendIdServerCmd.value = !!res.data
|
||||
if (canSendIdServerCmd.value) {
|
||||
const commands = res.data.split('\n').filter(i => i)
|
||||
console.log(commands)
|
||||
canControlMustLogin.value = commands.some(i => i.includes('must-login'))
|
||||
}
|
||||
}
|
||||
|
||||
const canControlMustLogin = ref(false)
|
||||
const refreshCanSendIdServerCmd = () => {
|
||||
checkCanSendIdServerCmd()
|
||||
}
|
||||
onMounted(refreshCanSendIdServerCmd)
|
||||
|
||||
const canSendRelayServerCmd = ref(false)
|
||||
|
||||
const checkCanSendRelayServerCmd = async () => {
|
||||
const res = await sendCmd({ cmd: 'h', target: RELAY_TARGET }).catch(_ => false)
|
||||
canSendRelayServerCmd.value = !!res.data
|
||||
}
|
||||
const refreshCanSendRelayServerCmd = () => {
|
||||
checkCanSendRelayServerCmd()
|
||||
}
|
||||
onMounted(refreshCanSendRelayServerCmd)
|
||||
|
||||
const rs = ref(null)
|
||||
const handleAlwaysUseRelaySuccess = () => {
|
||||
rs.value.save()
|
||||
}
|
||||
|
||||
const canSendCmd = (target) => {
|
||||
if (target === ID_TARGET) {
|
||||
return canSendIdServerCmd.value
|
||||
}
|
||||
if (target === RELAY_TARGET) {
|
||||
return canSendRelayServerCmd.value
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const listRes = reactive({
|
||||
list: [], total: 0, loading: false,
|
||||
})
|
||||
const listQuery = reactive({
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
})
|
||||
const getList = async () => {
|
||||
listRes.loading = true
|
||||
const res = await list(listQuery).catch(_ => false)
|
||||
listRes.loading = false
|
||||
if (res) {
|
||||
listRes.list = res.data.list
|
||||
listRes.total = res.data.total
|
||||
}
|
||||
}
|
||||
const handlerQuery = () => {
|
||||
if (listQuery.page === 1) {
|
||||
getList()
|
||||
} else {
|
||||
listQuery.page = 1
|
||||
}
|
||||
}
|
||||
onMounted(getList)
|
||||
const del = async (row) => {
|
||||
const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('Delete') }), {
|
||||
confirmButtonText: T('Confirm'),
|
||||
cancelButtonText: T('Cancel'),
|
||||
type: 'warning',
|
||||
}).catch(_ => false)
|
||||
if (!cf) {
|
||||
return false
|
||||
}
|
||||
|
||||
const res = await remove({ id: row.id }).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
getList()
|
||||
}
|
||||
}
|
||||
const formData = reactive({
|
||||
cmd: '',
|
||||
alias: '',
|
||||
option: '',
|
||||
target: '',
|
||||
explain: '',
|
||||
})
|
||||
const formVisible = ref(false)
|
||||
const toAdd = () => {
|
||||
formVisible.value = true
|
||||
formData.cmd = ''
|
||||
formData.alias = ''
|
||||
formData.option = ''
|
||||
formData.explain = ''
|
||||
}
|
||||
const toUpdate = (row) => {
|
||||
formVisible.value = true
|
||||
formData.id = row.id
|
||||
formData.cmd = row.cmd
|
||||
formData.alias = row.alias
|
||||
formData.option = row.option
|
||||
formData.target = row.target
|
||||
formData.explain = row.explain
|
||||
}
|
||||
const submit = async () => {
|
||||
if (!formData.cmd) {
|
||||
ElMessage.error(T('ParamRequired', { param: 'cmd' }))
|
||||
return
|
||||
}
|
||||
const api = formData.id ? update : create
|
||||
const res = await api(formData).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
formVisible.value = false
|
||||
getList()
|
||||
}
|
||||
}
|
||||
const cancel = () => {
|
||||
formVisible.value = false
|
||||
}
|
||||
|
||||
const showCmdForm = ref(false)
|
||||
const customCmd = reactive({
|
||||
cmd: '',
|
||||
option: '',
|
||||
target: '',
|
||||
res: '',
|
||||
example: '',
|
||||
})
|
||||
const showCmd = (row) => {
|
||||
showCmdForm.value = true
|
||||
customCmd.cmd = row.cmd
|
||||
customCmd.option = ''
|
||||
customCmd.res = ''
|
||||
customCmd.target = row.target
|
||||
customCmd.example = `${row.cmd} ${row.option}`
|
||||
}
|
||||
const submitCmd = async () => {
|
||||
sendCmd(customCmd).then(res => {
|
||||
console.log(res)
|
||||
customCmd.res = res.data
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.simple-card {
|
||||
min-width: 300px;
|
||||
margin: 10px;
|
||||
min-height: 300px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<el-card class="simple-card" shadow="hover" v-loading="form.loading">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>MUST_LOGIN</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :disabled="!canSend">
|
||||
<el-form-item>
|
||||
<el-switch v-model="form.option" active-value="Y" inactive-value="N"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="get">{{ T('Refresh') }}</el-button>
|
||||
<el-button @click="save" type="primary">{{ T('Save') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</template>
|
||||
<script setup>
|
||||
|
||||
import { T } from '@/utils/i18n'
|
||||
import { reactive, watch } from 'vue'
|
||||
import { sendCmd } from '@/api/rustdesk'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ID_TARGET } from '@/views/rustdesk/options'
|
||||
|
||||
const props = defineProps({
|
||||
canSend: Boolean,
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
cmd: 'ml',
|
||||
option: '',
|
||||
target: ID_TARGET,
|
||||
value: 0,
|
||||
loading: false,
|
||||
})
|
||||
const get = async () => {
|
||||
form.loading = true
|
||||
const res = await sendCmd({ cmd: 'ml', target: ID_TARGET }).catch(_ => false)
|
||||
form.loading = false
|
||||
if (res) {
|
||||
if (res.data === 'MUST_LOGIN: true' || res.data === 'MUST_LOGIN: true\n') {
|
||||
form.option = 'Y'
|
||||
} else {
|
||||
form.option = 'N'
|
||||
}
|
||||
}
|
||||
}
|
||||
const save = async () => {
|
||||
const res = await sendCmd(form).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.canSend, (v) => {
|
||||
if (v) {
|
||||
get()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
export const ID_TARGET = '21115'
|
||||
|
||||
export const RELAY_TARGET = '21117'
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<el-card class="simple-card" shadow="hover" v-loading="form.loading">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>RELAY_SERVERS</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :disabled="!canSend">
|
||||
<el-form-item>
|
||||
<el-input v-model="form.option"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="get">{{ T('Refresh') }}</el-button>
|
||||
<el-button @click="save" type="primary">{{ T('Save') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</template>
|
||||
<script setup>
|
||||
|
||||
import { T } from '@/utils/i18n'
|
||||
import { reactive, watch } from 'vue'
|
||||
import { sendCmd } from '@/api/rustdesk'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ID_TARGET } from '@/views/rustdesk/options'
|
||||
|
||||
const props = defineProps({
|
||||
canSend: Boolean,
|
||||
})
|
||||
|
||||
|
||||
const form = reactive({
|
||||
cmd: 'rs',
|
||||
option: '',
|
||||
target: ID_TARGET,
|
||||
loading: false,
|
||||
})
|
||||
const get = async () => {
|
||||
form.loading = true
|
||||
const res = await sendCmd({ cmd: 'rs', target: ID_TARGET }).catch(_ => false)
|
||||
form.loading = false
|
||||
if (res) {
|
||||
const data = res.data.split('\n').filter(i => i)
|
||||
form.option = data.join(',')
|
||||
}
|
||||
}
|
||||
const save = async () => {
|
||||
const res = await sendCmd(form).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
}
|
||||
}
|
||||
watch(() => props.canSend, (v) => {
|
||||
if (v) {
|
||||
get()
|
||||
}
|
||||
})
|
||||
//为了在设置always_use_relay之后自动重新保存,防止被重置
|
||||
defineExpose({
|
||||
save,
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<el-card class="simple-card" shadow="hover" v-loading="form.loading">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>USAGE</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :disabled="!canSend">
|
||||
<el-form-item>
|
||||
<el-table :data="form.list" size="small">
|
||||
<el-table-column prop="0" label="IP"></el-table-column>
|
||||
<el-table-column prop="1" label="TIME"></el-table-column>
|
||||
<el-table-column prop="2" label="TOTAL"></el-table-column>
|
||||
<el-table-column prop="3" label="HIGHEST"></el-table-column>
|
||||
<el-table-column prop="4" label="AVG"></el-table-column>
|
||||
<el-table-column prop="5" label="SPEED"></el-table-column>
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getList">{{ T('Refresh') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</template>
|
||||
<script setup>
|
||||
|
||||
import { T } from '@/utils/i18n'
|
||||
import { reactive, watch } from 'vue'
|
||||
import { sendCmd } from '@/api/rustdesk'
|
||||
import { RELAY_TARGET } from '@/views/rustdesk/options'
|
||||
|
||||
const props = defineProps({
|
||||
canSend: Boolean,
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
get_cmd: 'u',
|
||||
list: [],
|
||||
target: RELAY_TARGET,
|
||||
loading: false,
|
||||
})
|
||||
const getList = async () => {
|
||||
form.loading = true
|
||||
const res = await sendCmd({ cmd: form.get_cmd, target: RELAY_TARGET }).catch(_ => false)
|
||||
form.loading = false
|
||||
if (res) {
|
||||
form.list = res.data.split('\n').filter(i => i).map(i => i.split(" "))
|
||||
}
|
||||
}
|
||||
watch(() => props.canSend, (v) => {
|
||||
if (v) {
|
||||
getList()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.simple-card{
|
||||
width: 500px;
|
||||
}
|
||||
</style>
|
||||
@@ -15,7 +15,7 @@ export function useGetDetail (id) {
|
||||
form.value = { ...res.data }
|
||||
}
|
||||
if (id > 0) {
|
||||
onMounted(getDetail(id))
|
||||
onMounted(_ => {getDetail(id)})
|
||||
}
|
||||
|
||||
const getGroups = async () => {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { onMounted, reactive, watch } from 'vue'
|
||||
import { reactive } from 'vue'
|
||||
import { list, remove, changePwd } from '@/api/user'
|
||||
import { list as groups } from '@/api/group'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
import { T } from '@/utils/i18n'
|
||||
import { downBlob, jsonToCsv } from '@/utils/file'
|
||||
|
||||
export function useRepositories () {
|
||||
|
||||
@@ -42,18 +43,24 @@ export function useRepositories () {
|
||||
listRes.groups = res.data.list
|
||||
}
|
||||
}
|
||||
onMounted(getGroups)
|
||||
|
||||
onMounted(getList)
|
||||
|
||||
watch(() => listQuery.page, getList)
|
||||
watch(() => listQuery.page_size, handlerQuery)
|
||||
const toExport = async () => {
|
||||
const q = { ...listQuery }
|
||||
q.page_size = 1000000
|
||||
q.page = 1
|
||||
const res = await list(q).catch(_ => false)
|
||||
if (res) {
|
||||
const csv = jsonToCsv(res.data.list)
|
||||
downBlob(csv, 'users.csv')
|
||||
}
|
||||
}
|
||||
return {
|
||||
listRes,
|
||||
listQuery,
|
||||
handlerQuery,
|
||||
getList,
|
||||
getGroups,
|
||||
toExport,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
:inactive-value="DISABLE_STATUS"
|
||||
></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="T('Remark')" prop="remark">
|
||||
<el-input v-model="form.remark"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="cancel">{{ T('Cancel') }}</el-button>
|
||||
<el-button @click="submit" type="primary">{{ T('Submit') }}</el-button>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handlerQuery">{{ T('Filter') }}</el-button>
|
||||
<el-button type="danger" @click="toAdd">{{ T('Add') }}</el-button>
|
||||
<el-button type="success" @click="toExport">{{ T('Export') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
@@ -23,6 +24,16 @@
|
||||
<span v-else> - </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="T('Status')" align="center">
|
||||
<template #default="{row}">
|
||||
<el-switch v-model="row.status"
|
||||
:active-value="ENABLE_STATUS"
|
||||
:inactive-value="DISABLE_STATUS"
|
||||
@change="changeStatus(row)"
|
||||
></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" :label="T('Remark')" align="center"/>
|
||||
<el-table-column prop="created_at" :label="T('CreatedAt')" align="center"/>
|
||||
<el-table-column prop="updated_at" :label="T('UpdatedAt')" align="center"/>
|
||||
<el-table-column :label="T('Actions')" align="center" width="650">
|
||||
@@ -51,14 +62,27 @@
|
||||
<script setup>
|
||||
import { useRepositories, useDel, useToEditOrAdd, useChangePwd } from '@/views/user/composables'
|
||||
import { T } from '@/utils/i18n'
|
||||
import { DISABLE_STATUS, ENABLE_STATUS } from '@/utils/common_options'
|
||||
import { update } from '@/api/user'
|
||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
import { onMounted, watch } from 'vue'
|
||||
//列表
|
||||
const {
|
||||
listRes,
|
||||
listQuery,
|
||||
handlerQuery,
|
||||
getList,
|
||||
getGroups,
|
||||
toExport,
|
||||
} = useRepositories()
|
||||
|
||||
onMounted(getGroups)
|
||||
|
||||
onMounted(getList)
|
||||
|
||||
watch(() => listQuery.page, getList)
|
||||
watch(() => listQuery.page_size, handlerQuery)
|
||||
|
||||
const { toEdit, toAdd, toAddressBook, toTag } = useToEditOrAdd()
|
||||
|
||||
const { changePass } = useChangePwd()
|
||||
@@ -72,6 +96,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
const changeStatus = async (row) => {
|
||||
/*const confirm = await ElMessageBox.confirm(T('Confirm?', { param: T('Update') }), {
|
||||
confirmButtonText: T('Confirm'),
|
||||
cancelButtonText: T('Cancel'),
|
||||
}).catch(_ => false)
|
||||
if (!confirm) {
|
||||
return false
|
||||
}*/
|
||||
const res = await update(row).catch(_ => false)
|
||||
if (res) {
|
||||
ElMessage.success(T('OperationSuccess'))
|
||||
getList(listQuery)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
Référencer dans un nouveau ticket
Bloquer un utilisateur