Show guidelines on UI on how to setup network hook
This commit is contained in:
parent
d6c8964380
commit
4d0b4929c5
@ -86,3 +86,6 @@ pub const STORAGE_NAT_DIR: &str = "nat";
|
||||
|
||||
/// Environment variable that is set to run VirtWeb in NAT configuration mode
|
||||
pub const NAT_MODE_ENV_VAR_NAME: &str = "NAT_MODE";
|
||||
|
||||
/// Nat hook file path
|
||||
pub const NAT_HOOK_PATH: &str = "/etc/libvirt/hooks/network";
|
||||
|
@ -5,6 +5,7 @@ use crate::constants::{DISK_NAME_MAX_LEN, DISK_NAME_MIN_LEN, DISK_SIZE_MAX, DISK
|
||||
use crate::controllers::{HttpResult, LibVirtReq};
|
||||
use crate::extractors::local_auth_extractor::LocalAuthEnabled;
|
||||
use crate::libvirt_rest_structures::hypervisor::HypervisorInfo;
|
||||
use crate::nat::nat_hook;
|
||||
use crate::utils::net_utils;
|
||||
use actix_web::{HttpResponse, Responder};
|
||||
use sysinfo::{System, SystemExt};
|
||||
@ -120,6 +121,21 @@ pub async fn server_info(client: LibVirtReq) -> HttpResult {
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct NetworkHookStatus {
|
||||
installed: bool,
|
||||
content: String,
|
||||
path: &'static str,
|
||||
}
|
||||
|
||||
pub async fn network_hook_status() -> HttpResult {
|
||||
Ok(HttpResponse::Ok().json(NetworkHookStatus {
|
||||
installed: nat_hook::is_installed()?,
|
||||
content: nat_hook::hook_content()?,
|
||||
path: constants::NAT_HOOK_PATH,
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn number_vcpus() -> HttpResult {
|
||||
let mut system = System::new();
|
||||
system.refresh_cpu();
|
||||
|
@ -118,6 +118,10 @@ async fn main() -> std::io::Result<()> {
|
||||
"/api/server/info",
|
||||
web::get().to(server_controller::server_info),
|
||||
)
|
||||
.route(
|
||||
"/api/server/network_hook_status",
|
||||
web::get().to(server_controller::network_hook_status),
|
||||
)
|
||||
.route(
|
||||
"/api/server/number_vcpus",
|
||||
web::get().to(server_controller::number_vcpus),
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub mod nat_conf_mode;
|
||||
pub mod nat_definition;
|
||||
pub mod nat_hook;
|
||||
pub mod nat_lib;
|
||||
|
29
virtweb_backend/src/nat/nat_hook.rs
Normal file
29
virtweb_backend/src/nat/nat_hook.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use crate::app_config::AppConfig;
|
||||
use crate::constants;
|
||||
use std::path::Path;
|
||||
|
||||
/// Check out whether NAT hook has been installed or not
|
||||
pub fn is_installed() -> anyhow::Result<bool> {
|
||||
let hook_file = Path::new(constants::NAT_HOOK_PATH);
|
||||
|
||||
if !hook_file.exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let exe = std::env::current_exe()?;
|
||||
let hook_content = std::fs::read_to_string(hook_file)?;
|
||||
|
||||
Ok(hook_content.contains(exe.to_string_lossy().as_ref()))
|
||||
}
|
||||
|
||||
/// Get nat hook expected content
|
||||
pub fn hook_content() -> anyhow::Result<String> {
|
||||
let exe = std::env::current_exe()?;
|
||||
|
||||
Ok(format!(
|
||||
"#!/bin/bash\n\
|
||||
{} --storage {} --network-name \"$1\" --operation \"$2\" --sub-operation \"$3\"",
|
||||
exe.to_string_lossy(),
|
||||
AppConfig::get().storage
|
||||
))
|
||||
}
|
@ -148,6 +148,12 @@ interface SysLoadAverage {
|
||||
fifteen: number;
|
||||
}
|
||||
|
||||
export interface NetworkHookStatus {
|
||||
installed: boolean;
|
||||
content: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export class ServerApi {
|
||||
/**
|
||||
* Get server configuration
|
||||
@ -181,6 +187,18 @@ export class ServerApi {
|
||||
).data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get network hook status
|
||||
*/
|
||||
static async NetworkHookStatus(): Promise<NetworkHookStatus> {
|
||||
return (
|
||||
await APIClient.exec({
|
||||
method: "GET",
|
||||
uri: "/server/network_hook_status",
|
||||
})
|
||||
).data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get host supported vCPUs configurations
|
||||
*/
|
||||
|
@ -19,6 +19,7 @@ import { RouterLink } from "../widgets/RouterLink";
|
||||
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
|
||||
import { NetworkStatusWidget } from "../widgets/net/NetworkStatusWidget";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { NetworkHookStatusWidget } from "../widgets/net/NetworkHookStatusWidget";
|
||||
|
||||
export function NetworksListRoute(): React.ReactElement {
|
||||
const [list, setList] = React.useState<NetworkInfo[] | undefined>();
|
||||
@ -54,6 +55,8 @@ function NetworksListRouteInner(p: {
|
||||
</RouterLink>
|
||||
}
|
||||
>
|
||||
<NetworkHookStatusWidget hiddenIfInstalled />
|
||||
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
|
30
virtweb_frontend/src/widgets/CopyToClipboard.tsx
Normal file
30
virtweb_frontend/src/widgets/CopyToClipboard.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { ButtonBase } from "@mui/material";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
|
||||
|
||||
export function CopyToClipboard(
|
||||
p: PropsWithChildren<{ content: string }>
|
||||
): React.ReactElement {
|
||||
const snackbar = useSnackbar();
|
||||
|
||||
const copy = () => {
|
||||
navigator.clipboard.writeText(p.content);
|
||||
snackbar(`${p.content} copied to clipboard.`);
|
||||
};
|
||||
|
||||
return (
|
||||
<ButtonBase
|
||||
onClick={copy}
|
||||
style={{
|
||||
display: "inline-block",
|
||||
alignItems: "unset",
|
||||
textAlign: "unset",
|
||||
position: "relative",
|
||||
padding: "0px",
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
{p.children}
|
||||
</ButtonBase>
|
||||
);
|
||||
}
|
102
virtweb_frontend/src/widgets/net/NetworkHookStatusWidget.tsx
Normal file
102
virtweb_frontend/src/widgets/net/NetworkHookStatusWidget.tsx
Normal file
@ -0,0 +1,102 @@
|
||||
import React, { PropsWithChildren } from "react";
|
||||
import { AsyncWidget } from "../AsyncWidget";
|
||||
import { NetworkHookStatus, ServerApi } from "../../api/ServerApi";
|
||||
import { Alert, Typography } from "@mui/material";
|
||||
import { CopyToClipboard } from "../CopyToClipboard";
|
||||
|
||||
export function NetworkHookStatusWidget(p: {
|
||||
hiddenIfInstalled: boolean;
|
||||
}): React.ReactElement {
|
||||
const [status, setStatus] = React.useState<NetworkHookStatus | undefined>();
|
||||
|
||||
const load = async () => {
|
||||
setStatus(await ServerApi.NetworkHookStatus());
|
||||
};
|
||||
|
||||
return (
|
||||
<AsyncWidget
|
||||
loadKey={1}
|
||||
errMsg="Failed to get network status!"
|
||||
ready={!!status}
|
||||
load={load}
|
||||
build={() => <NetworkHookStatusWidgetInner {...p} status={status!} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function NetworkHookStatusWidgetInner(p: {
|
||||
status: NetworkHookStatus;
|
||||
hiddenIfInstalled: boolean;
|
||||
}): React.ReactElement {
|
||||
if (p.status.installed && p.hiddenIfInstalled) return <></>;
|
||||
if (p.status.installed)
|
||||
return (
|
||||
<Alert
|
||||
variant="outlined"
|
||||
severity="success"
|
||||
style={{ margin: "20px 0px" }}
|
||||
>
|
||||
The network hook has been installed on this system.
|
||||
</Alert>
|
||||
);
|
||||
|
||||
return (
|
||||
<Alert variant="outlined" severity="warning" style={{ margin: "20px 0px" }}>
|
||||
The network hook has not been detected on this system. It must be
|
||||
installed in order to expose ports from virtual machines through NAT on
|
||||
the network.
|
||||
<br />
|
||||
<br />
|
||||
In order to install it, please create a file named
|
||||
<CopyToClipboard content={p.status.path}>
|
||||
<InlineCode>{p.status.path}</InlineCode> with the following
|
||||
</CopyToClipboard>
|
||||
content:
|
||||
<br />
|
||||
<CopyToClipboard content={p.status.content}>
|
||||
<CodeBlock>{p.status.content}</CodeBlock>
|
||||
</CopyToClipboard>
|
||||
<br />
|
||||
You will need then to restart both <InlineCode>
|
||||
libvirtd
|
||||
</InlineCode> and <InlineCode>VirtWeb</InlineCode>.
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
function InlineCode(p: PropsWithChildren): React.ReactElement {
|
||||
return (
|
||||
<code
|
||||
style={{
|
||||
display: "inline-block",
|
||||
backgroundColor: "black",
|
||||
color: "white",
|
||||
wordBreak: "break-all",
|
||||
wordWrap: "break-word",
|
||||
whiteSpace: "pre-wrap",
|
||||
padding: "0px 7px",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
>
|
||||
{p.children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeBlock(p: PropsWithChildren): React.ReactElement {
|
||||
return (
|
||||
<pre
|
||||
style={{
|
||||
backgroundColor: "black",
|
||||
color: "white",
|
||||
wordBreak: "break-all",
|
||||
wordWrap: "break-word",
|
||||
whiteSpace: "pre-wrap",
|
||||
padding: "10px",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
>
|
||||
{p.children}
|
||||
</pre>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user