Fix VNC connection issue
This commit is contained in:
parent
7d88cd576c
commit
d1a9b6c3bb
@ -16,6 +16,7 @@ sudo apt install qemu-kvm libvirt-daemon-system
|
|||||||
3. Allow the current user to manage VMs:
|
3. Allow the current user to manage VMs:
|
||||||
```
|
```
|
||||||
sudo adduser $USER libvirt
|
sudo adduser $USER libvirt
|
||||||
|
sudo adduser $USER kvm
|
||||||
```
|
```
|
||||||
|
|
||||||
> Note: You will need to login again for this change to take effect.
|
> Note: You will need to login again for this change to take effect.
|
||||||
|
@ -6,14 +6,14 @@ use std::time::Duration;
|
|||||||
|
|
||||||
const TOKENS_CLEAN_INTERVAL: Duration = Duration::from_secs(60);
|
const TOKENS_CLEAN_INTERVAL: Duration = Duration::from_secs(60);
|
||||||
const VNC_TOKEN_LEN: usize = 15;
|
const VNC_TOKEN_LEN: usize = 15;
|
||||||
const VNC_TOKEN_LIFETIME: u64 = 120;
|
pub const VNC_TOKEN_LIFETIME: u64 = 30;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
enum VNCTokenError {
|
enum VNCTokenError {
|
||||||
#[error("Could not consume token, because it does not exist!")]
|
#[error("Could not use token, because it does not exist!")]
|
||||||
ConsumeErrorTokenNotFound,
|
UseErrorTokenNotFound,
|
||||||
#[error("Could not consume token, because it has expired!")]
|
#[error("Could not use token, because it has expired!")]
|
||||||
ConsumeErrorTokenExpired,
|
UseErrorTokenExpired,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -64,24 +64,22 @@ impl Handler<IssueTokenReq> for VNCTokensActor {
|
|||||||
|
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "anyhow::Result<XMLUuid>")]
|
#[rtype(result = "anyhow::Result<XMLUuid>")]
|
||||||
pub struct ConsumeTokenReq(String);
|
pub struct UseTokenReq(String);
|
||||||
|
|
||||||
impl Handler<ConsumeTokenReq> for VNCTokensActor {
|
impl Handler<UseTokenReq> for VNCTokensActor {
|
||||||
type Result = anyhow::Result<XMLUuid>;
|
type Result = anyhow::Result<XMLUuid>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: ConsumeTokenReq, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: UseTokenReq, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
log::debug!("Attempt to consume a token {:?}", msg.0);
|
log::debug!("Attempt to use a token {:?}", msg.0);
|
||||||
|
|
||||||
let token_index = self
|
let token = self
|
||||||
.0
|
.0
|
||||||
.iter()
|
.iter()
|
||||||
.position(|i| i.token == msg.0)
|
.find(|i| i.token == msg.0)
|
||||||
.ok_or(VNCTokenError::ConsumeErrorTokenNotFound)?;
|
.ok_or(VNCTokenError::UseErrorTokenNotFound)?;
|
||||||
|
|
||||||
let token = self.0.remove(token_index);
|
|
||||||
|
|
||||||
if token.is_expired() {
|
if token.is_expired() {
|
||||||
return Err(VNCTokenError::ConsumeErrorTokenExpired.into());
|
return Err(VNCTokenError::UseErrorTokenExpired.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(token.vm)
|
Ok(token.vm)
|
||||||
@ -101,8 +99,8 @@ impl VNCTokensManager {
|
|||||||
self.0.send(IssueTokenReq(id)).await?
|
self.0.send(IssueTokenReq(id)).await?
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consume a VNC access token
|
/// Use a VNC access token
|
||||||
pub async fn consume_token(&self, token: String) -> anyhow::Result<XMLUuid> {
|
pub async fn use_token(&self, token: String) -> anyhow::Result<XMLUuid> {
|
||||||
self.0.send(ConsumeTokenReq(token)).await?
|
self.0.send(UseTokenReq(token)).await?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::actors::vnc_tokens_actor::VNC_TOKEN_LIFETIME;
|
||||||
use crate::app_config::AppConfig;
|
use crate::app_config::AppConfig;
|
||||||
use crate::constants;
|
use crate::constants;
|
||||||
use crate::constants::{DISK_NAME_MAX_LEN, DISK_NAME_MIN_LEN, DISK_SIZE_MAX, DISK_SIZE_MIN};
|
use crate::constants::{DISK_NAME_MAX_LEN, DISK_NAME_MIN_LEN, DISK_SIZE_MAX, DISK_SIZE_MIN};
|
||||||
@ -29,6 +30,7 @@ struct LenConstraints {
|
|||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
struct ServerConstraints {
|
struct ServerConstraints {
|
||||||
iso_max_size: usize,
|
iso_max_size: usize,
|
||||||
|
vnc_token_duration: u64,
|
||||||
vm_name_size: LenConstraints,
|
vm_name_size: LenConstraints,
|
||||||
vm_title_size: LenConstraints,
|
vm_title_size: LenConstraints,
|
||||||
memory_size: LenConstraints,
|
memory_size: LenConstraints,
|
||||||
@ -47,6 +49,8 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
|
|||||||
constraints: ServerConstraints {
|
constraints: ServerConstraints {
|
||||||
iso_max_size: constants::ISO_MAX_SIZE,
|
iso_max_size: constants::ISO_MAX_SIZE,
|
||||||
|
|
||||||
|
vnc_token_duration: VNC_TOKEN_LIFETIME,
|
||||||
|
|
||||||
vm_name_size: LenConstraints { min: 2, max: 50 },
|
vm_name_size: LenConstraints { min: 2, max: 50 },
|
||||||
vm_title_size: LenConstraints { min: 0, max: 50 },
|
vm_title_size: LenConstraints { min: 0, max: 50 },
|
||||||
memory_size: LenConstraints {
|
memory_size: LenConstraints {
|
||||||
|
@ -269,7 +269,7 @@ pub async fn vnc(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
stream: web::Payload,
|
stream: web::Payload,
|
||||||
) -> HttpResult {
|
) -> HttpResult {
|
||||||
let domain_id = manager.consume_token(token.0.token).await?;
|
let domain_id = manager.use_token(token.0.token).await?;
|
||||||
let domain = client.get_single_domain(domain_id).await?;
|
let domain = client.get_single_domain(domain_id).await?;
|
||||||
|
|
||||||
let socket_path = match domain.devices.graphics {
|
let socket_path = match domain.devices.graphics {
|
||||||
|
@ -10,6 +10,7 @@ export interface ServerConfig {
|
|||||||
|
|
||||||
export interface ServerConstraints {
|
export interface ServerConstraints {
|
||||||
iso_max_size: number;
|
iso_max_size: number;
|
||||||
|
vnc_token_duration: number;
|
||||||
vm_name_size: LenConstraint;
|
vm_name_size: LenConstraint;
|
||||||
vm_title_size: LenConstraint;
|
vm_title_size: LenConstraint;
|
||||||
memory_size: LenConstraint;
|
memory_size: LenConstraint;
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { VncScreen } from "react-vnc";
|
import { VncScreen } from "react-vnc";
|
||||||
|
import { ServerApi } from "../api/ServerApi";
|
||||||
import { VMApi, VMInfo } from "../api/VMApi";
|
import { VMApi, VMInfo } from "../api/VMApi";
|
||||||
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
|
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
|
||||||
|
import { time } from "../utils/DateUtils";
|
||||||
import { AsyncWidget } from "../widgets/AsyncWidget";
|
import { AsyncWidget } from "../widgets/AsyncWidget";
|
||||||
|
|
||||||
|
interface VNCTokenInfo {
|
||||||
|
url: string;
|
||||||
|
expire: number;
|
||||||
|
}
|
||||||
|
|
||||||
export function VNCRoute(): React.ReactElement {
|
export function VNCRoute(): React.ReactElement {
|
||||||
const { uuid } = useParams();
|
const { uuid } = useParams();
|
||||||
|
|
||||||
@ -27,35 +34,43 @@ export function VNCRoute(): React.ReactElement {
|
|||||||
function VNCInner(p: { vm: VMInfo }): React.ReactElement {
|
function VNCInner(p: { vm: VMInfo }): React.ReactElement {
|
||||||
const snackbar = useSnackbar();
|
const snackbar = useSnackbar();
|
||||||
|
|
||||||
const counter = React.useRef(false);
|
const [token, setToken] = React.useState<VNCTokenInfo | undefined>();
|
||||||
const [url, setURL] = React.useState<string | undefined>();
|
const [counter, setCounter] = React.useState(1);
|
||||||
|
|
||||||
const load = async () => {
|
const connect = async (force: boolean) => {
|
||||||
try {
|
try {
|
||||||
|
if (force) setCounter(counter + 1);
|
||||||
|
|
||||||
|
// Check if getting new time is useless
|
||||||
|
if ((token?.expire ?? 0) > time()) return;
|
||||||
|
|
||||||
|
setToken(undefined);
|
||||||
|
|
||||||
const u = await VMApi.OneShotVNCURL(p.vm);
|
const u = await VMApi.OneShotVNCURL(p.vm);
|
||||||
console.info(u);
|
|
||||||
if (counter.current === false) {
|
if (!token)
|
||||||
counter.current = true;
|
setToken({
|
||||||
setURL(u);
|
expire: time() + ServerApi.Config.constraints.vnc_token_duration,
|
||||||
}
|
url: u,
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
snackbar("Failed to initialize VNC connection!");
|
snackbar("Failed to initialize VNC connection!");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const reconnect = () => {
|
const disconnected = () => {
|
||||||
counter.current = false;
|
connect(true);
|
||||||
setURL(undefined);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
connect(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (token === undefined)
|
||||||
|
return <p>Please wait, connecting to the machine...</p>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncWidget
|
|
||||||
loadKey={counter.current}
|
|
||||||
load={load}
|
|
||||||
ready={url !== undefined && counter.current}
|
|
||||||
errMsg="Failed to get a token to initialize VNC connection!"
|
|
||||||
build={() => (
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -65,14 +80,12 @@ function VNCInner(p: { vm: VMInfo }): React.ReactElement {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<VncScreen
|
<VncScreen
|
||||||
url={url!}
|
url={token!.url}
|
||||||
onDisconnect={() => {
|
onDisconnect={() => {
|
||||||
console.info("VNC disconnected " + url);
|
console.info("VNC disconnected " + token?.url);
|
||||||
reconnect();
|
disconnected();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
6
virtweb_frontend/src/utils/DateUtils.ts
Normal file
6
virtweb_frontend/src/utils/DateUtils.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Get current UNIX time, in seconds
|
||||||
|
*/
|
||||||
|
export function time(): number {
|
||||||
|
return Math.floor(new Date().getTime() / 1000);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user