Reintroduce P2P Settings (#2365)

* redo backend to be less cringe

* fixed up
This commit is contained in:
Oscar Beaumont 2024-04-22 20:43:44 +08:00 committed by GitHub
parent ef969f1ada
commit 959ccdfd98
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 166 additions and 125 deletions

View file

@ -1,7 +1,7 @@
use crate::{
invalidate_query,
node::{
config::{NodeConfig, NodePreferences, P2PDiscoveryState, Port},
config::{NodeConfig, NodeConfigP2P, NodePreferences, P2PDiscoveryState},
get_hardware_model_name, HardwareModel,
},
old_job::JobProgressEvent,
@ -84,8 +84,7 @@ pub struct SanitisedNodeConfig {
/// name is the display name of the current node. This is set by the user and is shown in the UI. // TODO: Length validation so it can fit in DNS record
pub name: String,
pub identity: RemoteIdentity,
pub p2p_ipv4_port: Port,
pub p2p_ipv6_port: Port,
pub p2p: NodeConfigP2P,
pub p2p_discovery: P2PDiscoveryState,
pub features: Vec<BackendFeature>,
pub preferences: NodePreferences,
@ -98,8 +97,7 @@ impl From<NodeConfig> for SanitisedNodeConfig {
id: value.id,
name: value.name,
identity: value.identity.to_remote_identity(),
p2p_ipv4_port: value.p2p_ipv4_port,
p2p_ipv6_port: value.p2p_ipv6_port,
p2p: value.p2p,
p2p_discovery: value.p2p_discovery,
features: value.features,
preferences: value.preferences,

View file

@ -19,8 +19,9 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
#[derive(Deserialize, Type)]
pub struct ChangeNodeNameArgs {
pub name: Option<String>,
pub p2p_ipv4_port: Option<Port>,
pub p2p_ipv6_port: Option<Port>,
pub p2p_port: Option<Port>,
pub p2p_ipv4_enabled: Option<bool>,
pub p2p_ipv6_enabled: Option<bool>,
pub p2p_discovery: Option<P2PDiscoveryState>,
pub image_labeler_version: Option<String>,
}
@ -43,14 +44,14 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
config.name = name;
}
if let Some(port) = args.p2p_ipv4_port {
config.p2p_ipv4_port = port;
if let Some(port) = args.p2p_port {
config.p2p.port = port;
};
if let Some(port) = args.p2p_ipv6_port {
config.p2p_ipv6_port = port;
if let Some(enabled) = args.p2p_ipv4_enabled {
config.p2p.ipv4 = enabled;
};
if let Some(v) = args.p2p_discovery {
config.p2p_discovery = v;
if let Some(enabled) = args.p2p_ipv6_enabled {
config.p2p.ipv6 = enabled;
};
#[cfg(feature = "ai")]

View file

@ -37,20 +37,54 @@ pub enum P2PDiscoveryState {
}
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Type)]
#[serde(rename_all = "snake_case", untagged)]
#[serde(rename_all = "snake_case", tag = "type", content = "value")]
pub enum Port {
Disabled,
#[default]
Random,
Discrete(u16),
}
impl Port {
pub fn get(&self) -> u16 {
match self {
Port::Random => 0,
Port::Discrete(port) => *port,
}
}
pub fn is_random(&self) -> bool {
matches!(self, Port::Random)
}
}
fn default_as_true() -> bool {
true
}
fn skip_if_true(value: &bool) -> bool {
*value == true
}
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
pub struct NodeConfigP2P {
#[serde(default, skip_serializing_if = "Port::is_random")]
pub port: Port,
#[serde(default = "default_as_true", skip_serializing_if = "skip_if_true")]
pub ipv4: bool,
#[serde(default = "default_as_true", skip_serializing_if = "skip_if_true")]
pub ipv6: bool,
}
impl Default for NodeConfigP2P {
fn default() -> Self {
Self {
port: Port::Random,
ipv4: true,
ipv6: true,
}
}
}
/// NodeConfig is the configuration for a node. This is shared between all libraries and is stored in a JSON file on disk.
#[derive(Debug, Clone, Serialize, Deserialize)] // If you are adding `specta::Type` on this your probably about to leak the P2P private key
pub struct NodeConfig {
@ -66,10 +100,8 @@ pub struct NodeConfig {
#[serde(with = "identity_serde")]
pub identity: Identity,
/// P2P config
#[serde(default, skip_serializing_if = "Port::is_random")]
pub p2p_ipv4_port: Port,
#[serde(default, skip_serializing_if = "Port::is_random")]
pub p2p_ipv6_port: Port,
#[serde(default)]
pub p2p: NodeConfigP2P,
#[serde(default)]
pub p2p_discovery: P2PDiscoveryState,
/// Feature flags enabled on the node
@ -153,8 +185,7 @@ impl ManagedVersion<NodeConfigVersion> for NodeConfig {
id: Uuid::new_v4(),
name,
identity: Identity::default(),
p2p_ipv4_port: Port::Random,
p2p_ipv6_port: Port::Random,
p2p: NodeConfigP2P::default(),
p2p_discovery: P2PDiscoveryState::Everyone,
version: Self::LATEST_VERSION,
features: vec![],

View file

@ -1,6 +1,6 @@
use crate::{
node::{
config::{self, P2PDiscoveryState, Port},
config::{self, P2PDiscoveryState},
get_hardware_model_name, HardwareModel,
},
p2p::{
@ -146,18 +146,19 @@ impl P2PManager {
}
.update(&mut self.p2p.metadata_mut());
let port = match config.p2p_ipv4_port {
Port::Disabled => None,
Port::Random => Some(0),
Port::Discrete(port) => Some(port),
};
info!("Setting quic ipv4 listener to: {port:?}");
if let Err(err) = self.quic.set_ipv4_enabled(port).await {
let port = config.p2p.port.get();
info!(
"Setting quic ipv4 listener to: {:?}",
config.p2p.ipv4.then(|| port)
);
if let Err(err) = self
.quic
.set_ipv4_enabled(config.p2p.ipv4.then(|| port))
.await
{
error!("Failed to enabled quic ipv4 listener: {err}");
self.node_config
.write(|c| c.p2p_ipv4_port = Port::Disabled)
.await
.ok();
self.node_config.write(|c| c.p2p.ipv4 = false).await.ok();
self.listener_errors
.lock()
@ -165,18 +166,17 @@ impl P2PManager {
.ipv4 = Some(format!("{err}"));
}
let port = match config.p2p_ipv6_port {
Port::Disabled => None,
Port::Random => Some(0),
Port::Discrete(port) => Some(port),
};
info!("Setting quic ipv6 listener to: {port:?}");
if let Err(err) = self.quic.set_ipv6_enabled(port).await {
info!(
"Setting quic ipv6 listener to: {:?}",
config.p2p.ipv6.then(|| port)
);
if let Err(err) = self
.quic
.set_ipv6_enabled(config.p2p.ipv6.then(|| port))
.await
{
error!("Failed to enabled quic ipv6 listener: {err}");
self.node_config
.write(|c| c.p2p_ipv6_port = Port::Disabled)
.await
.ok();
self.node_config.write(|c| c.p2p.ipv6 = false).await.ok();
self.listener_errors
.lock()
@ -270,11 +270,7 @@ impl P2PManager {
"name": name,
"listener_addrs": listeners.iter().find(|l| l.is_hook_id(*id)).map(|l| l.addrs.clone()),
})).collect::<Vec<_>>(),
"config": json!({
"p2p_ipv4_port": node_config.p2p_ipv4_port,
"p2p_ipv6_port": node_config.p2p_ipv6_port,
"p2p_discovery": node_config.p2p_discovery,
}),
"config": node_config.p2p,
"relay_config": self.quic.get_relay_config(),
})
}

View file

@ -1,4 +1,5 @@
import { FormProvider } from 'react-hook-form';
import clsx from 'clsx';
import { Controller, FormProvider } from 'react-hook-form';
import {
useBridgeMutation,
useBridgeQuery,
@ -37,6 +38,8 @@ const LANGUAGE_OPTIONS = [
// Sort the languages by their label
LANGUAGE_OPTIONS.sort((a, b) => a.label.localeCompare(b.label));
const u16 = () => z.number().min(0).max(65535);
export const Component = () => {
const node = useBridgeQuery(['nodeState']);
const platform = usePlatform();
@ -50,9 +53,12 @@ export const Component = () => {
schema: z
.object({
name: z.string().min(1).max(250).optional(),
// p2p_enabled: z.boolean().optional(),
// p2p_port: u16,
// customOrDefault: z.enum(['Custom', 'Default']),
p2p_port: z.discriminatedUnion('type', [
z.object({ type: z.literal('random') }),
z.object({ type: z.literal('discrete'), value: u16() })
]),
p2p_ipv4_enabled: z.boolean().optional(),
p2p_ipv6_enabled: z.boolean().optional(),
image_labeler_version: z.string().optional(),
background_processing_percentage: z.coerce
.number({
@ -66,25 +72,26 @@ export const Component = () => {
reValidateMode: 'onChange',
defaultValues: {
name: node.data?.name,
// p2p_port: node.data?.p2p_port || 0,
// p2p_enabled: node.data?.p2p_enabled,
// customOrDefault: node.data?.p2p_port ? 'Custom' : 'Default',
p2p_port: node.data?.p2p.port || { type: 'random' },
p2p_ipv4_enabled: node.data?.p2p.ipv4 || true,
p2p_ipv6_enabled: node.data?.p2p.ipv6 || true,
image_labeler_version: node.data?.image_labeler_version ?? undefined,
background_processing_percentage:
node.data?.preferences.thumbnailer.background_processing_percentage || 50
}
});
const p2p_port = form.watch('p2p_port');
// const watchCustomOrDefault = form.watch('customOrDefault');
// const watchP2pEnabled = form.watch('p2p_enabled');
const watchBackgroundProcessingPercentage = form.watch('background_processing_percentage');
useDebouncedFormWatch(form, async (value) => {
if (await form.trigger()) {
await editNode.mutateAsync({
name: value.name || null,
p2p_ipv4_port: null,
p2p_ipv6_port: null,
p2p_port: (value.p2p_port as any) ?? null,
p2p_ipv4_enabled: value.p2p_ipv4_enabled ?? null,
p2p_ipv6_enabled: value.p2p_ipv6_enabled ?? null,
p2p_discovery: null,
// p2p_port: value.customOrDefault === 'Default' ? 0 : Number(value.p2p_port),
// p2p_enabled: value.p2p_enabled ?? null,
@ -101,11 +108,11 @@ export const Component = () => {
node.refetch();
});
// form.watch((data) => {
// if (Number(data.p2p_port) > 65535) {
// form.setValue('p2p_port', 65535);
// }
// });
form.watch((data) => {
if (data.p2p_port?.type == 'discrete' && Number(data.p2p_port.value) > 65535) {
form.setValue('p2p_port', { type: 'discrete', value: 65535 });
}
});
const { t } = useLocale();
@ -288,20 +295,10 @@ export const Component = () => {
/>
</div>
</Setting> */}
{/* <div className="flex flex-col gap-4">
<h1 className="mb-3 text-lg font-bold text-ink">{t('networking')}</h1> */}
<div className="flex flex-col gap-4">
<h1 className="mb-3 text-lg font-bold text-ink">{t('networking')}</h1>
{/* TODO: Add some UI for this stuff */}
{/* {node.data?.p2p.ipv4.status === 'Listening' ||
node.data?.p2p.ipv4.status === 'Enabling'
? `0.0.0.0:${node.data?.p2p.ipv4?.port || 0}`
: ''}
{node.data?.p2p.ipv6.status === 'Listening' ||
node.data?.p2p.ipv6.status === 'Enabling'
? `[::1]:${node.data?.p2p.ipv6?.port || 0}`
: ''} */}
{/* <Setting
<Setting
mini
title={t('enable_networking')}
description={
@ -317,56 +314,69 @@ export const Component = () => {
>
<Switch
size="md"
// checked={watchP2pEnabled || false}
// onClick={() => form.setValue('p2p_enabled', !form.getValues('p2p_enabled'))}
// disabled
onClick={() => toast.info(t('coming_soon'))}
checked={form.watch('p2p_ipv4_enabled') && form.watch('p2p_ipv6_enabled')}
onCheckedChange={(checked) => {
form.setValue('p2p_ipv4_enabled', checked);
form.setValue('p2p_ipv6_enabled', checked);
}}
/>
</Setting> */}
{/* <Setting
mini
title={t('networking_port')}
description={t('networking_port_description')}
>
<div className="flex h-[30px] gap-2">
<Controller
control={form.control}
name="customOrDefault"
render={({ field }) => (
</Setting>
{form.watch('p2p_ipv4_enabled') && form.watch('p2p_ipv6_enabled') ? (
<>
<Setting
mini
title={t('networking_port')}
description={t('networking_port_description')}
>
<div className="flex h-[30px] gap-2">
<Select
value={p2p_port.type}
containerClassName="h-[30px]"
disabled={!watchP2pEnabled}
className={clsx(!watchP2pEnabled && 'opacity-50', 'h-full')}
{...field}
onChange={(e) => {
field.onChange(e);
form.setValue('p2p_port', 0);
className="h-full"
onChange={(type) => {
form.setValue('p2p_port', {
type: type as any
});
}}
>
<SelectOption value="Default">{t('default')}</SelectOption>
<SelectOption value="Custom">{t('custom')}</SelectOption>
<SelectOption value="random">{t('random')}</SelectOption>
<SelectOption value="discrete">{t('custom')}</SelectOption>
</Select>
)}
/>
<Input
className={clsx(
'w-[66px]',
watchCustomOrDefault === 'Default' || !watchP2pEnabled
? 'opacity-50'
: 'opacity-100'
)}
disabled={watchCustomOrDefault === 'Default' || !watchP2pEnabled}
{...form.register('p2p_port')}
onChange={(e) => {
form.setValue(
'p2p_port',
Number(e.target.value.replace(/[^0-9]/g, ''))
);
}}
/>
</div>
</Setting> */}
{/* </div> */}
<Input
value={p2p_port.type === 'discrete' ? p2p_port.value : 0}
className={clsx(
'w-[66px]',
p2p_port.type === 'random' ? 'opacity-50' : 'opacity-100'
)}
disabled={p2p_port.type === 'random'}
onChange={(e) => {
form.setValue('p2p_port', {
type: 'discrete',
value: Number(e.target.value.replace(/[^0-9]/g, ''))
});
}}
/>
</div>
</Setting>
<Setting
mini
title={t('ipv6')}
description={
<p className="text-sm text-gray-400">{t('ipv6_description')}</p>
}
>
<Switch
size="md"
checked={form.watch('p2p_ipv6_enabled')}
onCheckedChange={(checked) =>
form.setValue('p2p_ipv6_enabled', checked)
}
/>
</Setting>
</>
) : null}
</div>
</FormProvider>
);
};

View file

@ -94,6 +94,9 @@
"debug_mode": "Debug mode",
"debug_mode_description": "Enable extra debugging features within the app.",
"default": "Default",
"random": "Random",
"ipv6": "IPv6 networking",
"ipv6_description": "Allow peer-to-peer communication using IPv6 networking",
"default_settings": "Default Settings",
"delete": "Delete",
"delete_dialog_title": "Delete {{prefix}} {{type}}",

View file

@ -169,7 +169,7 @@ export type CacheNode = { __type: string; __id: string; "#node": any }
export type CameraData = { device_make: string | null; device_model: string | null; color_space: string | null; color_profile: ColorProfile | null; focal_length: number | null; shutter_speed: number | null; flash: Flash | null; orientation: Orientation; lens_make: string | null; lens_model: string | null; bit_depth: number | null; red_eye: boolean | null; zoom: number | null; iso: number | null; software: string | null; serial_number: string | null; lens_serial_number: string | null; contrast: number | null; saturation: number | null; sharpness: number | null; composite: Composite | null }
export type ChangeNodeNameArgs = { name: string | null; p2p_ipv4_port: Port | null; p2p_ipv6_port: Port | null; p2p_discovery: P2PDiscoveryState | null; image_labeler_version: string | null }
export type ChangeNodeNameArgs = { name: string | null; p2p_port: Port | null; p2p_ipv4_enabled: boolean | null; p2p_ipv6_enabled: boolean | null; p2p_discovery: P2PDiscoveryState | null; image_labeler_version: string | null }
export type CloudInstance = { id: string; uuid: string; identity: RemoteIdentity; nodeId: string; metadata: { [key in string]: string } }
@ -450,6 +450,8 @@ export type MediaLocation = { latitude: number; longitude: number; pluscode: Plu
export type MediaMetadata = ({ type: "Image" } & ImageMetadata) | ({ type: "Video" } & VideoMetadata) | ({ type: "Audio" } & AudioMetadata)
export type NodeConfigP2P = { port: Port; ipv4: boolean; ipv6: boolean }
export type NodePreferences = { thumbnailer: ThumbnailerPreferences }
export type NodeState = ({
@ -460,7 +462,7 @@ id: string;
/**
* name is the display name of the current node. This is set by the user and is shown in the UI. // TODO: Length validation so it can fit in DNS record
*/
name: string; identity: RemoteIdentity; p2p_ipv4_port: Port; p2p_ipv6_port: Port; p2p_discovery: P2PDiscoveryState; features: BackendFeature[]; preferences: NodePreferences; image_labeler_version: string | null }) & { data_path: string; device_model: string | null }
name: string; identity: RemoteIdentity; p2p: NodeConfigP2P; p2p_discovery: P2PDiscoveryState; features: BackendFeature[]; preferences: NodePreferences; image_labeler_version: string | null }) & { data_path: string; device_model: string | null }
export type NonIndexedPathItem = { path: string; name: string; extension: string; kind: number; is_dir: boolean; date_created: string; date_modified: string; size_in_bytes_bytes: number[]; hidden: boolean }
@ -539,7 +541,7 @@ export type PeerMetadata = { name: string; operating_system: OperatingSystem | n
export type PlusCode = string
export type Port = null | number
export type Port = { type: "random" } | { type: "discrete"; value: number }
export type Range<T> = { from: T } | { to: T }