add share to guest by web client
This commit is contained in:
+6
-3
@@ -7,14 +7,16 @@
|
|||||||
"serve": "vite preview"
|
"serve": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "1.6.0",
|
"axios": "1.7.4",
|
||||||
"element-plus": "^2.8.2",
|
"element-plus": "^2.8.2",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"pinia": "2.0.3",
|
"pinia": "2.0.3",
|
||||||
"vue": "3.2.37",
|
"vue": "3.2.37",
|
||||||
"vue-router": "^4.0.12"
|
"vue-router": "^4.0.12",
|
||||||
|
"fast-sha256": "^1.3.0",
|
||||||
|
"clipboard": "2.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@element-plus/icons": "0.0.11",
|
"@element-plus/icons": "0.0.11",
|
||||||
@@ -23,6 +25,7 @@
|
|||||||
"qs": "^6.10.2",
|
"qs": "^6.10.2",
|
||||||
"sass-loader": "^12.3.0",
|
"sass-loader": "^12.3.0",
|
||||||
"sass": "^1.43.4",
|
"sass": "^1.43.4",
|
||||||
"vite": "^2.9.18"
|
"vite": "^2.9.18",
|
||||||
|
"ts-proto": "^1.141.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,3 +44,12 @@ export function batchCreate (data) {
|
|||||||
data,
|
data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shareByWebClient (data) {
|
||||||
|
return request({
|
||||||
|
url: '/address_book/shareByWebClient',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import Clipboard from 'clipboard'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { T } from '@/utils/i18n'
|
||||||
|
|
||||||
|
export function handleClipboard (text, event) {
|
||||||
|
const clipboard = new Clipboard(event.target.toString(), {
|
||||||
|
text: () => text,
|
||||||
|
})
|
||||||
|
clipboard.on('success', () => {
|
||||||
|
ElMessage.success(T('CopySuccess'))
|
||||||
|
clipboard.destroy()
|
||||||
|
})
|
||||||
|
clipboard.on('error', () => {
|
||||||
|
ElMessage.error(T('CopyFailed'))
|
||||||
|
clipboard.destroy()
|
||||||
|
})
|
||||||
|
clipboard.onClick(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function copyImage (targetNode) {
|
||||||
|
if (window.getSelection) {
|
||||||
|
// chrome等主流浏览器
|
||||||
|
var selection = window.getSelection()
|
||||||
|
selection.removeAllRanges()
|
||||||
|
var range = document.createRange()
|
||||||
|
range.selectNode(targetNode)
|
||||||
|
selection.addRange(range)
|
||||||
|
} else if (document.body.createTextRange) {
|
||||||
|
console.log('IE')
|
||||||
|
// ie
|
||||||
|
const range = document.body.createTextRange()
|
||||||
|
range.moveToElementText(targetNode)
|
||||||
|
range.select()
|
||||||
|
}
|
||||||
|
document.execCommand('copy')
|
||||||
|
}
|
||||||
@@ -282,5 +282,70 @@
|
|||||||
},
|
},
|
||||||
"PleaseSelectData": {
|
"PleaseSelectData": {
|
||||||
"One": "Please select data"
|
"One": "Please select data"
|
||||||
|
},
|
||||||
|
"PasswordType": {
|
||||||
|
"One": "Password Type"
|
||||||
|
},
|
||||||
|
"OncePassword": {
|
||||||
|
"One": "One-time Password"
|
||||||
|
},
|
||||||
|
"FixedPassword": {
|
||||||
|
"One": "Fixed Password"
|
||||||
|
},
|
||||||
|
"FixedPasswordWarning": {
|
||||||
|
"One": "Fixed passwords may be leaked, so please use them with caution and use one-time passwords is recommended"
|
||||||
|
},
|
||||||
|
"ExpireTime": {
|
||||||
|
"One": "Expire Time"
|
||||||
|
},
|
||||||
|
"ShareByWebClient": {
|
||||||
|
"One": "Share By Web Client"
|
||||||
|
},
|
||||||
|
"Minutes": {
|
||||||
|
"One": "{param} Minute",
|
||||||
|
"Other": "{param} Minutes"
|
||||||
|
},
|
||||||
|
"Hours": {
|
||||||
|
"One": "{param} Hour",
|
||||||
|
"Other": "{param} Hours"
|
||||||
|
},
|
||||||
|
"Days": {
|
||||||
|
"One": "{param} Day",
|
||||||
|
"Other": "{param} Days"
|
||||||
|
},
|
||||||
|
"Weeks": {
|
||||||
|
"One": "{param} Week",
|
||||||
|
"Other": "{param} Weeks"
|
||||||
|
},
|
||||||
|
"Months": {
|
||||||
|
"One": "{param} Month",
|
||||||
|
"Other": "{param} Months"
|
||||||
|
},
|
||||||
|
"Forever": {
|
||||||
|
"One": "Forever"
|
||||||
|
},
|
||||||
|
"Error": {
|
||||||
|
"One": "Error"
|
||||||
|
},
|
||||||
|
"IDNotExist": {
|
||||||
|
"One": "ID does not exist"
|
||||||
|
},
|
||||||
|
"RemoteDesktopOffline": {
|
||||||
|
"One": "Remote desktop is offline"
|
||||||
|
},
|
||||||
|
"KeyMismatch": {
|
||||||
|
"One": "Key mismatch"
|
||||||
|
},
|
||||||
|
"KeyOveruse": {
|
||||||
|
"One": "Key overuse"
|
||||||
|
},
|
||||||
|
"Link": {
|
||||||
|
"One": "Link"
|
||||||
|
},
|
||||||
|
"CopySuccess": {
|
||||||
|
"One": "Copy Success"
|
||||||
|
},
|
||||||
|
"CopyFailed": {
|
||||||
|
"One": "Copy Failed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,5 +274,65 @@
|
|||||||
},
|
},
|
||||||
"PleaseSelectData": {
|
"PleaseSelectData": {
|
||||||
"One": "请选择数据"
|
"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": "复制失败"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ service.interceptors.request.use(
|
|||||||
const app = useAppStore()
|
const app = useAppStore()
|
||||||
const lang = app.setting.lang
|
const lang = app.setting.lang
|
||||||
if (lang) {
|
if (lang) {
|
||||||
console.log('lang', lang)
|
// console.log('lang', lang)
|
||||||
config.headers['Accept-Language'] = lang
|
config.headers['Accept-Language'] = lang
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+84
-1
@@ -1,5 +1,10 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { config } from '@/api/rustdesk'
|
import { config } from '@/api/rustdesk'
|
||||||
|
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'
|
||||||
|
|
||||||
export const toWebClientLink = (row) => {
|
export const toWebClientLink = (row) => {
|
||||||
window.open(`${rustdeskConfig.value.api_server}/webclient/#/?id=${row.id}`)
|
window.open(`${rustdeskConfig.value.api_server}/webclient/#/?id=${row.id}`)
|
||||||
@@ -23,4 +28,82 @@ export function loadRustdeskConfig () {
|
|||||||
rustdeskConfig,
|
rustdeskConfig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const { rustdeskConfig } = loadRustdeskConfig()
|
|
||||||
|
export const { rustdeskConfig } = loadRustdeskConfig()
|
||||||
|
export async function getPeerSlat (id) {
|
||||||
|
const [addr, port] = rustdeskConfig.value.id_server.split(':')
|
||||||
|
if (!addr) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const scheme = location.protocol === 'https:' ? 'wss' : 'ws'
|
||||||
|
const ws = new Websock(`${scheme}://${addr}:21118`, true)
|
||||||
|
await ws.open()
|
||||||
|
const conn_type = rendezvous.ConnType.DEFAULT_CONN
|
||||||
|
const nat_type = rendezvous.NatType.SYMMETRIC
|
||||||
|
const punch_hole_request = rendezvous.PunchHoleRequest.fromPartial({
|
||||||
|
id,
|
||||||
|
licence_key: rustdeskConfig.value.key || undefined,
|
||||||
|
conn_type,
|
||||||
|
nat_type,
|
||||||
|
token: undefined,
|
||||||
|
})
|
||||||
|
ws.sendRendezvous({ punch_hole_request })
|
||||||
|
//rendezvous.RendezvousMessage
|
||||||
|
const msg = (await ws.next())
|
||||||
|
ws.close()
|
||||||
|
console.log(new Date() + ': Got relay response', msg)
|
||||||
|
const phr = msg.punch_hole_response
|
||||||
|
const rr = msg.relay_response
|
||||||
|
if (phr) {
|
||||||
|
if (phr?.other_failure) {
|
||||||
|
this.msgbox('error', 'Error', phr?.other_failure)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (phr.failure != rendezvous.PunchHoleResponse_Failure.UNRECOGNIZED) {
|
||||||
|
switch (phr?.failure) {
|
||||||
|
case rendezvous.PunchHoleResponse_Failure.ID_NOT_EXIST:
|
||||||
|
ElMessageBox.alert(T('IDNotExist'), T('Error'))
|
||||||
|
break
|
||||||
|
case rendezvous.PunchHoleResponse_Failure.OFFLINE:
|
||||||
|
ElMessageBox.alert(T('RemoteDesktopOffline'), T('Error'))
|
||||||
|
break
|
||||||
|
case rendezvous.PunchHoleResponse_Failure.LICENSE_MISMATCH:
|
||||||
|
ElMessageBox.alert(T('KeyMismatch'), T('Error'))
|
||||||
|
break
|
||||||
|
case rendezvous.PunchHoleResponse_Failure.LICENSE_OVERUSE:
|
||||||
|
ElMessageBox.alert(T('KeyOveruse'), T('Error'))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} else if (rr) {
|
||||||
|
const uuid = rr.uuid
|
||||||
|
console.log(new Date() + ': Connecting to relay server')
|
||||||
|
|
||||||
|
const _ws = new Websock(`${scheme}://${addr}:21119`, false)
|
||||||
|
await _ws.open()
|
||||||
|
console.log(new Date() + ': Connected to relay server')
|
||||||
|
const request_relay = rendezvous.RequestRelay.fromPartial({
|
||||||
|
licence_key: rustdeskConfig.value.key || undefined,
|
||||||
|
uuid,
|
||||||
|
})
|
||||||
|
_ws.sendRendezvous({ request_relay })
|
||||||
|
|
||||||
|
//暂不支持pk
|
||||||
|
const public_key = message.PublicKey.fromPartial({})
|
||||||
|
_ws?.sendMessage({ public_key })
|
||||||
|
// const secure = (await this.secure(pk)) || false;
|
||||||
|
// globals.pushEvent("connection_ready", { secure, direct: false });
|
||||||
|
while (true) {
|
||||||
|
const msg = (await _ws?.next())
|
||||||
|
console.log('msg', msg)
|
||||||
|
if (msg?.hash) {
|
||||||
|
console.log('hash msg.....', msg.hash)
|
||||||
|
_ws.close()
|
||||||
|
return msg.hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,183 @@
|
|||||||
|
import * as message from "./message.js";
|
||||||
|
import * as rendezvous from "./rendezvous.js";
|
||||||
|
|
||||||
|
type Keys = "message" | "open" | "close" | "error";
|
||||||
|
|
||||||
|
export default class Websock {
|
||||||
|
_websocket: WebSocket;
|
||||||
|
_eventHandlers: { [key in Keys]: Function };
|
||||||
|
_buf: (rendezvous.RendezvousMessage | message.Message)[];
|
||||||
|
_status: any;
|
||||||
|
_latency: number;
|
||||||
|
_secretKey: [Uint8Array, number, number] | undefined;
|
||||||
|
_uri: string;
|
||||||
|
_isRendezvous: boolean;
|
||||||
|
|
||||||
|
constructor(uri: string, isRendezvous: boolean = true) {
|
||||||
|
this._eventHandlers = {
|
||||||
|
message: (_: any) => {},
|
||||||
|
open: () => {},
|
||||||
|
close: () => {},
|
||||||
|
error: () => {},
|
||||||
|
};
|
||||||
|
this._uri = uri;
|
||||||
|
this._status = "";
|
||||||
|
this._buf = [];
|
||||||
|
this._websocket = new WebSocket(uri);
|
||||||
|
this._websocket.onmessage = this._recv_message.bind(this);
|
||||||
|
this._websocket.binaryType = "arraybuffer";
|
||||||
|
this._latency = new Date().getTime();
|
||||||
|
this._isRendezvous = isRendezvous;
|
||||||
|
}
|
||||||
|
|
||||||
|
latency(): number {
|
||||||
|
return this._latency;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSecretKey(key: Uint8Array) {
|
||||||
|
this._secretKey = [key, 0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(json: message.DeepPartial<message.Message>) {
|
||||||
|
let data = message.Message.encode(
|
||||||
|
message.Message.fromPartial(json)
|
||||||
|
).finish();
|
||||||
|
// let k = this._secretKey;
|
||||||
|
// if (k) {
|
||||||
|
// k[1] += 1;
|
||||||
|
// data = globals.encrypt(data, k[1], k[0]);
|
||||||
|
// }
|
||||||
|
this._websocket.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRendezvous(data: rendezvous.DeepPartial<rendezvous.RendezvousMessage>) {
|
||||||
|
this._websocket.send(
|
||||||
|
rendezvous.RendezvousMessage.encode(
|
||||||
|
rendezvous.RendezvousMessage.fromPartial(data)
|
||||||
|
).finish()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseMessage(data: Uint8Array) {
|
||||||
|
return message.Message.decode(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseRendezvous(data: Uint8Array) {
|
||||||
|
return rendezvous.RendezvousMessage.decode(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event Handlers
|
||||||
|
off(evt: Keys) {
|
||||||
|
this._eventHandlers[evt] = () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
on(evt: Keys, handler: Function) {
|
||||||
|
this._eventHandlers[evt] = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
async open(timeout: number = 12000): Promise<Websock> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this._status != "open") {
|
||||||
|
reject(this._status || "Timeout");
|
||||||
|
}
|
||||||
|
}, timeout);
|
||||||
|
this._websocket.onopen = () => {
|
||||||
|
this._latency = new Date().getTime() - this._latency;
|
||||||
|
this._status = "open";
|
||||||
|
console.debug(">> WebSock.onopen");
|
||||||
|
if (this._websocket?.protocol) {
|
||||||
|
console.info(
|
||||||
|
"Server choose sub-protocol: " + this._websocket.protocol
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._eventHandlers.open();
|
||||||
|
console.info("WebSock.onopen");
|
||||||
|
resolve(this);
|
||||||
|
};
|
||||||
|
this._websocket.onclose = (e) => {
|
||||||
|
if (this._status == "open") {
|
||||||
|
// e.code 1000 means that the connection was closed normally.
|
||||||
|
//
|
||||||
|
}
|
||||||
|
this._status = e;
|
||||||
|
console.error("WebSock.onclose: ");
|
||||||
|
console.error(e);
|
||||||
|
this._eventHandlers.close(e);
|
||||||
|
reject("Reset by the peer");
|
||||||
|
};
|
||||||
|
this._websocket.onerror = (e: any) => {
|
||||||
|
if (!this._status) {
|
||||||
|
reject("Failed to connect to " + (this._isRendezvous ? "rendezvous" : "relay") + " server");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._status = e;
|
||||||
|
console.error("WebSock.onerror: ")
|
||||||
|
console.error(e);
|
||||||
|
this._eventHandlers.error(e);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async next(
|
||||||
|
timeout = 12000
|
||||||
|
): Promise<rendezvous.RendezvousMessage | message.Message> {
|
||||||
|
const func = (
|
||||||
|
resolve: (value: rendezvous.RendezvousMessage | message.Message) => void,
|
||||||
|
reject: (reason: any) => void,
|
||||||
|
tm0: number
|
||||||
|
) => {
|
||||||
|
// console.log('next')
|
||||||
|
if (this._buf.length) {
|
||||||
|
resolve(this._buf[0]);
|
||||||
|
this._buf.splice(0, 1);
|
||||||
|
} else {
|
||||||
|
if (this._status != "open") {
|
||||||
|
reject(this._status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (new Date().getTime() > tm0 + timeout) {
|
||||||
|
reject("Timeout");
|
||||||
|
} else {
|
||||||
|
setTimeout(() => func(resolve, reject, tm0), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
func(resolve, reject, new Date().getTime());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this._status = "";
|
||||||
|
if (this._websocket) {
|
||||||
|
if (
|
||||||
|
this._websocket.readyState === WebSocket.OPEN ||
|
||||||
|
this._websocket.readyState === WebSocket.CONNECTING
|
||||||
|
) {
|
||||||
|
console.info("Closing WebSocket connection");
|
||||||
|
this._websocket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._websocket.onmessage = () => {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_recv_message(e: any) {
|
||||||
|
if (e.data instanceof window.ArrayBuffer) {
|
||||||
|
let bytes = new Uint8Array(e.data);
|
||||||
|
// const k = this._secretKey;
|
||||||
|
// if (k) {
|
||||||
|
// k[2] += 1;
|
||||||
|
// bytes = globals.decrypt(bytes, k[2], k[0]);
|
||||||
|
// }
|
||||||
|
this._buf.push(
|
||||||
|
this._isRendezvous
|
||||||
|
? this.parseRendezvous(bytes)
|
||||||
|
: this.parseMessage(bytes)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this._eventHandlers.message(e.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="shareform" :model="formData" label-width="120px" label-suffix=" :">
|
||||||
|
<el-form-item :label="T('ID')" prop="id" required>
|
||||||
|
{{ formData.id }}
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="T('PasswordType')">
|
||||||
|
<div>
|
||||||
|
<el-radio-group v-model="formData.password_type" @change="changePwdType">
|
||||||
|
<el-radio value="once">{{ T('OncePassword') }}</el-radio>
|
||||||
|
<el-radio value="fixed">{{ T('FixedPassword') }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<div v-if="formData.password_type==='fixed'" style="color: red">
|
||||||
|
{{ T('FixedPasswordWarning') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="T('Password')" prop="password" required>
|
||||||
|
<el-input v-model="formData.password" type="password" show-password></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="T('ExpireTime')" prop="expire" required>
|
||||||
|
<el-select v-model="formData.expire">
|
||||||
|
<el-option
|
||||||
|
v-for="item in expireTimes"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="link" :label="T('Link')">
|
||||||
|
<el-input v-model="link" readonly>
|
||||||
|
<template #append>
|
||||||
|
<el-button :icon="CopyDocument" @click="copyLink"/>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button v-if="!link" @click="cancel">{{ T('Cancel') }}</el-button>
|
||||||
|
<el-button v-if="!link" :loading="loading" @click="submitShare" type="primary">{{ T('Submit') }}</el-button>
|
||||||
|
<el-button v-else @click="cancel" type="success">{{ T('Close') }}</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { T } from '@/utils/i18n'
|
||||||
|
import { computed, reactive, ref, watch } from 'vue'
|
||||||
|
import { getPeerSlat, rustdeskConfig } from '@/utils/webclient'
|
||||||
|
import * as sha256 from 'fast-sha256'
|
||||||
|
import { shareByWebClient } from '@/api/address_book'
|
||||||
|
import { CopyDocument } from '@element-plus/icons'
|
||||||
|
import { handleClipboard } from '@/utils/clipboard'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
id: String,
|
||||||
|
hash: String,
|
||||||
|
})
|
||||||
|
const emits = defineEmits(['cancel', 'success'])
|
||||||
|
const formData = reactive({
|
||||||
|
id: props.id,
|
||||||
|
password_type: 'once',
|
||||||
|
password: '',
|
||||||
|
expire: 1800,
|
||||||
|
hash: props.hash,
|
||||||
|
})
|
||||||
|
watch(() => props.id, () => {
|
||||||
|
init()
|
||||||
|
})
|
||||||
|
const init = () => {
|
||||||
|
console.log('init')
|
||||||
|
formData.id = props.id
|
||||||
|
formData.hash = props.hash
|
||||||
|
formData.password = ''
|
||||||
|
formData.expire = 300
|
||||||
|
formData.password_type = 'once'
|
||||||
|
link.value = ''
|
||||||
|
}
|
||||||
|
const link = ref('')
|
||||||
|
const expireTimes = computed(() => [
|
||||||
|
{ label: T('Minutes', { param: 5 }, 5), value: 300 },
|
||||||
|
{ label: T('Minutes', { param: 30 }, 30), value: 1800 },
|
||||||
|
{ label: T('Hours', { param: 1 }, 1), value: 3600 },
|
||||||
|
{ label: T('Days', { param: 1 }, 1), value: 86400 },
|
||||||
|
{ label: T('Weeks', { param: 1 }, 1), value: 604800 },
|
||||||
|
{ label: T('Months', { param: 1 }, 1), value: 2592000 },
|
||||||
|
{ label: T('Forever'), value: 0 },
|
||||||
|
])
|
||||||
|
const changePwdType = (val) => {
|
||||||
|
if (val === 'fixed' && !formData.password) {
|
||||||
|
formData.password = props.hash
|
||||||
|
}
|
||||||
|
if (val === 'once') {
|
||||||
|
formData.password = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const cancel = () => {
|
||||||
|
loading.value = false
|
||||||
|
emits('cancel')
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
const loading = ref(false)
|
||||||
|
const submitShare = async () => {
|
||||||
|
if (!formData.password) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loading.value = true
|
||||||
|
const _formData = { ...formData }
|
||||||
|
if (formData.password !== formData.hash) {
|
||||||
|
const res = await getPeerSlat(formData.id).catch(_ => false)
|
||||||
|
if (!res) {
|
||||||
|
loading.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const p = hash([formData.password, res.salt])
|
||||||
|
_formData.password = btoa(p.toString().split(',').map((v) => String.fromCharCode(v)).join(''))
|
||||||
|
}
|
||||||
|
const res = await shareByWebClient(_formData).catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
link.value = `${rustdeskConfig.value.api_server}/webclient/#/?share_token=${res.data.share_token}`
|
||||||
|
emits('success')
|
||||||
|
}
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyLink = (e) => {
|
||||||
|
handleClipboard(link.value, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hash = (datas) => {
|
||||||
|
const hasher = new sha256.Hash()
|
||||||
|
datas.forEach((data) => {
|
||||||
|
if (typeof data == 'string') {
|
||||||
|
data = new TextEncoder().encode(data)
|
||||||
|
}
|
||||||
|
hasher.update(data)
|
||||||
|
})
|
||||||
|
return hasher.digest()
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -116,7 +116,16 @@ export function useRepositories (user_id) {
|
|||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const shareToWebClientVisible = ref(false)
|
||||||
|
const shareToWebClientForm = reactive({
|
||||||
|
id: '',
|
||||||
|
hash: '',
|
||||||
|
})
|
||||||
|
const toShowShare = (row) => {
|
||||||
|
shareToWebClientForm.id = row.id
|
||||||
|
shareToWebClientForm.hash = row.hash
|
||||||
|
shareToWebClientVisible.value = true
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
listRes,
|
listRes,
|
||||||
listQuery,
|
listQuery,
|
||||||
@@ -129,5 +138,8 @@ export function useRepositories (user_id) {
|
|||||||
toEdit,
|
toEdit,
|
||||||
toAdd,
|
toAdd,
|
||||||
submit,
|
submit,
|
||||||
|
shareToWebClientVisible,
|
||||||
|
shareToWebClientForm,
|
||||||
|
toShowShare,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,9 +44,10 @@
|
|||||||
<el-table-column prop="tags" :label="T('Tags')" align="center"/>
|
<el-table-column prop="tags" :label="T('Tags')" align="center"/>
|
||||||
<!-- <el-table-column prop="created_at" label="创建时间" align="center"/>-->
|
<!-- <el-table-column prop="created_at" label="创建时间" align="center"/>-->
|
||||||
<!-- <el-table-column prop="updated_at" label="更新时间" align="center"/>-->
|
<!-- <el-table-column prop="updated_at" label="更新时间" align="center"/>-->
|
||||||
<el-table-column label="操作" align="center" width="400">
|
<el-table-column label="操作" align="center" width="500">
|
||||||
<template #default="{row}">
|
<template #default="{row}">
|
||||||
<el-button type="success" @click="toWebClientLink(row)">Web-Client</el-button>
|
<el-button type="success" @click="toWebClientLink(row)">Web Client</el-button>
|
||||||
|
<!-- <el-button type="primary" @click="toShowShare(row)">{{ T('ShareByWebClient') }}</el-button>-->
|
||||||
<el-button @click="toEdit(row)">{{ T('Edit') }}</el-button>
|
<el-button @click="toEdit(row)">{{ T('Edit') }}</el-button>
|
||||||
<el-button type="danger" @click="del(row)">{{ T('Delete') }}</el-button>
|
<el-button type="danger" @click="del(row)">{{ T('Delete') }}</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -139,6 +140,12 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
<!-- <el-dialog v-model="shareToWebClientVisible" width="900" :close-on-click-modal="false">
|
||||||
|
<shareByWebClient :id="shareToWebClientForm.id"
|
||||||
|
:hash="shareToWebClientForm.hash"
|
||||||
|
@cancel="shareToWebClientVisible=false"
|
||||||
|
@success=""/>
|
||||||
|
</el-dialog>-->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -147,9 +154,10 @@
|
|||||||
import { list as fetchTagList } from '@/api/tag'
|
import { list as fetchTagList } from '@/api/tag'
|
||||||
import { loadAllUsers } from '@/global'
|
import { loadAllUsers } from '@/global'
|
||||||
import { useRepositories } from '@/views/address_book/index'
|
import { useRepositories } from '@/views/address_book/index'
|
||||||
import { toWebClientLink } from '@/utils/webclient'
|
import { toWebClientLink, getPeerSlat } from '@/utils/webclient'
|
||||||
import { T } from '@/utils/i18n'
|
import { T } from '@/utils/i18n'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
import shareByWebClient from '@/views/address_book/components/shareByWebClient.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const { allUsers, getAllUsers } = loadAllUsers()
|
const { allUsers, getAllUsers } = loadAllUsers()
|
||||||
@@ -178,7 +186,9 @@
|
|||||||
toEdit,
|
toEdit,
|
||||||
toAdd,
|
toAdd,
|
||||||
submit,
|
submit,
|
||||||
currentColor,
|
shareToWebClientVisible,
|
||||||
|
shareToWebClientForm,
|
||||||
|
toShowShare,
|
||||||
} = useRepositories()
|
} = useRepositories()
|
||||||
|
|
||||||
if (route.query?.user_id) {
|
if (route.query?.user_id) {
|
||||||
@@ -192,6 +202,7 @@
|
|||||||
|
|
||||||
watch(() => listQuery.page_size, handlerQuery)
|
watch(() => listQuery.page_size, handlerQuery)
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -29,9 +29,10 @@
|
|||||||
<el-table-column prop="tags" :label="T('Tags')" align="center"/>
|
<el-table-column prop="tags" :label="T('Tags')" align="center"/>
|
||||||
<!-- <el-table-column prop="created_at" label="创建时间" align="center"/>-->
|
<!-- <el-table-column prop="created_at" label="创建时间" align="center"/>-->
|
||||||
<!-- <el-table-column prop="updated_at" label="更新时间" align="center"/>-->
|
<!-- <el-table-column prop="updated_at" label="更新时间" align="center"/>-->
|
||||||
<el-table-column :label="T('Actions')" align="center" width="400">
|
<el-table-column :label="T('Actions')" align="center" width="500">
|
||||||
<template #default="{row}">
|
<template #default="{row}">
|
||||||
<el-button type="success" @click="toWebClientLink(row)">Web-Client</el-button>
|
<el-button type="success" @click="toWebClientLink(row)">Web Client</el-button>
|
||||||
|
<el-button type="primary" @click="toShowShare(row)">{{ T('ShareByWebClient') }}</el-button>
|
||||||
<el-button @click="toEdit(row)">{{ T('Edit') }}</el-button>
|
<el-button @click="toEdit(row)">{{ T('Edit') }}</el-button>
|
||||||
<el-button type="danger" @click="del(row)">{{ T('Delete') }}</el-button>
|
<el-button type="danger" @click="del(row)">{{ T('Delete') }}</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -114,6 +115,12 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
<el-dialog v-model="shareToWebClientVisible" width="900" :close-on-click-modal="false">
|
||||||
|
<shareByWebClient :id="shareToWebClientForm.id"
|
||||||
|
:hash="shareToWebClientForm.hash"
|
||||||
|
@cancel="shareToWebClientVisible=false"
|
||||||
|
@success=""/>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -123,6 +130,7 @@
|
|||||||
import { useRepositories } from '@/views/address_book'
|
import { useRepositories } from '@/views/address_book'
|
||||||
import { toWebClientLink } from '@/utils/webclient'
|
import { toWebClientLink } from '@/utils/webclient'
|
||||||
import { T } from '@/utils/i18n'
|
import { T } from '@/utils/i18n'
|
||||||
|
import shareByWebClient from '@/views/address_book/components/shareByWebClient.vue'
|
||||||
|
|
||||||
const tagList = ref([])
|
const tagList = ref([])
|
||||||
const fetchTagListData = async () => {
|
const fetchTagListData = async () => {
|
||||||
@@ -145,6 +153,9 @@
|
|||||||
toEdit,
|
toEdit,
|
||||||
toAdd,
|
toAdd,
|
||||||
submit,
|
submit,
|
||||||
|
shareToWebClientVisible,
|
||||||
|
shareToWebClientForm,
|
||||||
|
toShowShare,
|
||||||
} = useRepositories()
|
} = useRepositories()
|
||||||
|
|
||||||
listQuery.is_my = 1
|
listQuery.is_my = 1
|
||||||
|
|||||||
+2
-2
@@ -31,8 +31,8 @@ const conf = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
target: 'es2015',
|
target: 'es2020',
|
||||||
minify: 'terser', // 是否进行压缩,boolean | 'terser' | 'esbuild',默认使用 esbuild
|
minify: 'esbuild', // 是否进行压缩,boolean | 'terser' | 'esbuild',默认使用 esbuild
|
||||||
manifest: false, // 是否产出maifest.json
|
manifest: false, // 是否产出maifest.json
|
||||||
sourcemap: false, // 是否产出soucemap.json
|
sourcemap: false, // 是否产出soucemap.json
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user