Update ConfigForm.svelte

Signed-off-by: Simon Fondrie-Teitler <simonft@riseup.net>
This commit is contained in:
Simon Fondrie-Teitler
2025-09-18 12:18:10 -04:00
committed by Cooper Quintin
parent 1cf6f5d339
commit 1b5575e5a6
10 changed files with 170 additions and 24 deletions
+66 -2
View File
@@ -1,14 +1,22 @@
use std::path::Path;
use std::{path::Path, time::Duration};
use log::{error, info};
use rayhunter::Device;
use serde::Serialize;
use tokio::select;
use tokio_util::{sync::CancellationToken, task::TaskTracker};
use crate::error::RayhunterError;
use crate::{
error::RayhunterError,
notifications::{Notification, NotificationType},
};
pub mod orbic;
pub mod tmobile;
pub mod wingtech;
const LOW_BATTERY_LEVEL: u8 = 10;
#[derive(Clone, Copy, PartialEq, Debug, Serialize)]
pub struct BatteryState {
level: u8,
@@ -45,3 +53,59 @@ pub async fn get_battery_status(device: &Device) -> Result<BatteryState, Rayhunt
_ => return Err(RayhunterError::FunctionNotSupportedForDeviceError),
})
}
pub fn run_battery_notification_worker(
task_tracker: &TaskTracker,
device: Device,
notification_channel: tokio::sync::mpsc::Sender<Notification>,
shutdown_token: CancellationToken,
) {
task_tracker.spawn(async move {
// Don't send a notification initially if the device starts at a low battery level.
let mut triggered = match get_battery_status(&device).await {
Err(RayhunterError::FunctionNotSupportedForDeviceError) => {
info!("Battery level function not supported for device");
false
}
Err(e) => {
error!("Failed to get battery status: {e}");
true
}
Ok(status) => status.level < LOW_BATTERY_LEVEL,
};
loop {
select! {
_ = shutdown_token.cancelled() => break,
_ = tokio::time::sleep(Duration::from_secs(15)) => {}
}
let status = match get_battery_status(&device).await {
Err(e) => {
error!("Failed to get battery status: {e}");
continue;
}
Ok(status) => status,
};
// To avoid flapping, if the notification has already been triggered
// wait until the device has been plugged in and the battery level
// is high enough to re-enable notifications.
if triggered && status.is_plugged_in && status.level > LOW_BATTERY_LEVEL {
triggered = false;
continue;
}
if !triggered && !status.is_plugged_in && status.level <= LOW_BATTERY_LEVEL {
notification_channel
.send(Notification::new(
NotificationType::LowBattery,
"Rayhunter's battery is low".to_string(),
None,
))
.await
.expect("Failed to send to notification channel");
triggered = true;
}
}
});
}
+3
View File
@@ -5,6 +5,7 @@ use rayhunter::Device;
use rayhunter::analysis::analyzer::AnalyzerConfig;
use crate::error::RayhunterError;
use crate::notifications::NotificationType;
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default)]
@@ -17,6 +18,7 @@ pub struct Config {
pub colorblind_mode: bool,
pub key_input_mode: u8,
pub ntfy_url: Option<String>,
pub enabled_notifications: Vec<NotificationType>,
pub analyzers: AnalyzerConfig,
}
@@ -32,6 +34,7 @@ impl Default for Config {
key_input_mode: 0,
analyzers: AnalyzerConfig::default(),
ntfy_url: None,
enabled_notifications: vec![NotificationType::Warning, NotificationType::LowBattery],
}
}
}
+2 -2
View File
@@ -24,7 +24,7 @@ use rayhunter::qmdl::QmdlWriter;
use crate::analysis::{AnalysisCtrlMessage, AnalysisWriter};
use crate::display;
use crate::notifications::Notification;
use crate::notifications::{Notification, NotificationType};
use crate::qmdl_store::{RecordingStore, RecordingStoreError};
use crate::server::ServerState;
@@ -207,7 +207,7 @@ impl DiagTask {
info!("a heuristic triggered on this run!");
self.notification_channel
.send(Notification::new(
"heuristic-warning".to_string(),
NotificationType::Warning,
format!("Rayhunter has detected a {:?} severity event", max_type),
Some(Duration::from_secs(60 * 5)),
))
+15 -1
View File
@@ -14,6 +14,7 @@ mod stats;
use std::net::SocketAddr;
use std::sync::Arc;
use crate::battery::run_battery_notification_worker;
use crate::config::{parse_args, parse_config};
use crate::diag::run_diag_read_thread;
use crate::error::RayhunterError;
@@ -260,7 +261,20 @@ async fn run_with_config(
qmdl_store_lock.clone(),
analysis_tx.clone(),
);
run_notification_worker(&task_tracker, notification_service);
run_battery_notification_worker(
&task_tracker,
config.device.clone(),
notification_service.new_handler(),
shutdown_token.clone(),
);
run_notification_worker(
&task_tracker,
notification_service,
config.enabled_notifications.clone(),
);
let state = Arc::new(ServerState {
config_path: args.config_path.clone(),
config,
+20 -4
View File
@@ -5,19 +5,30 @@ use std::{
};
use log::error;
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc::{self, error::TryRecvError};
use tokio_util::task::TaskTracker;
#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
pub enum NotificationType {
Warning,
LowBattery,
}
pub struct Notification {
message_type: String,
notification_type: NotificationType,
message: String,
debounce: Option<Duration>,
}
impl Notification {
pub fn new(message_type: String, message: String, debounce: Option<Duration>) -> Self {
pub fn new(
notification_type: NotificationType,
message: String,
debounce: Option<Duration>,
) -> Self {
Notification {
message_type,
notification_type,
message,
debounce,
}
@@ -52,6 +63,7 @@ impl NotificationService {
pub fn run_notification_worker(
task_tracker: &TaskTracker,
mut notification_service: NotificationService,
enabled_notifications: Vec<NotificationType>,
) {
task_tracker.spawn(async move {
if let Some(url) = notification_service.url
@@ -65,8 +77,12 @@ pub fn run_notification_worker(
loop {
match notification_service.rx.try_recv() {
Ok(notification) => {
if !enabled_notifications.contains(&notification.notification_type) {
continue;
}
let status = notification_statuses
.entry(notification.message_type)
.entry(notification.notification_type)
.or_insert_with(|| NotificationStatus {
message: "".to_string(),
needs_sending: true,
+50 -12
View File
@@ -111,18 +111,6 @@
</select>
</div>
<div>
<label for="ntfy_url" class="block text-sm font-medium text-gray-700 mb-1">
ntfy URL for Sending Notifications
</label>
<input
id="ntfy_url"
type="url"
bind:value={config.ntfy_url}
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue"
/>
</div>
<div class="space-y-3">
<div class="flex items-center">
<input
@@ -137,6 +125,56 @@
</div>
</div>
<div class="border-t pt-4 mt-6 space-y-3">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Notification Settings</h3>
<div>
<label for="ntfy_url" class="block text-sm font-medium text-gray-700 mb-1">
ntfy URL for Sending Notifications (if unset you will not receive
notifications)
</label>
<input
id="ntfy_url"
type="url"
bind:value={config.ntfy_url}
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue"
/>
</div>
<div class="space-y-2">
<div class="block text-sm font-medium text-gray-700 mb-1">
Enabled Notification Types
</div>
<div class="flex items-center">
<input
type="checkbox"
id="enable_warning_notifications"
value="Warning"
bind:group={config.enabled_notifications}
/>
<label
for="enable_warning_notifications"
class="ml-2 block text-sm text-gray-700"
>
Warnings
</label>
</div>
<div class="flex items-center">
<input
type="checkbox"
id="enable_lowbattery_notifications"
value="LowBattery"
bind:group={config.enabled_notifications}
/>
<label
for="enable_lowbattery_notifications"
class="ml-2 block text-sm text-gray-700"
>
Low Battery
</label>
</div>
</div>
</div>
<div class="border-t pt-4 mt-6">
<h3 class="text-lg font-semibold text-gray-800 mb-4">
Analyzer Heuristic Settings
+6
View File
@@ -12,11 +12,17 @@ export interface AnalyzerConfig {
test_analyzer: boolean;
}
export enum enabled_notifications {
Warning = 'Warning',
LowBattery = 'LowBattery',
}
export interface Config {
ui_level: number;
colorblind_mode: boolean;
key_input_mode: number;
ntfy_url: string;
enabled_notifications: enabled_notifications[];
analyzers: AnalyzerConfig;
}
+4 -2
View File
@@ -23,7 +23,9 @@ ui_level = 1
key_input_mode = 0
# If set, attempts to send a notification to the url when a new warning is triggered
# ntfy_url =
ntfy_url = ""
# What notification types to enable. Does nothing if the above ntfy_url is not set.
enabled_notifications = ["Warning", "LowBattery"]
# Analyzer Configuration
# Enable/disable specific IMSI catcher detection heuristics
@@ -35,4 +37,4 @@ lte_sib6_and_7_downgrade = true
null_cipher = true
nas_null_cipher = true
incomplete_sib = true
test_analyzer = false
test_analyzer = false
+4 -1
View File
@@ -13,8 +13,11 @@ Through web UI you can set:
- **Device Input Mode**, which defines behaviour of built-in power button of the device. *Device Input Mode* could be:
- *Disable button control*: built-in power button of the device is not used by Rayhunter;
- *Double-tap power button to start/stop recording*: double clicking on a built-in power button of the device stops and immediately restarts the recording. This could be useful if Rayhunter's heuristichs is triggered and you get the red line, and you want to "reset" the past warnings. Normally you can do that through web UI, but sometimes it is easier to double tap on power button.
- **ntfy URL for Sending Notifications**, which allows setting a [ntfy](https://ntfy.sh/) URL to which notifications of new detections will be sent. The topic should be unique to your device, e.g., `https://ntfy.sh/rayhunter_notifications_ba9di7ie` or `https://myserver.example.com/rayhunter_notifications_ba9di7ie`. The ntfy Android and iOS apps can then be used to receive notifications. More information can be found in the [ntfy docs](https://docs.ntfy.sh/).
- **Colorblind Mode** enables color blind mode (blue line is shown instead of green line, red line remains red). Please note that this does not cover all types of color blindness, but switching green to blue should be about enough to differentiate the color change for most types of color blindness.
- **ntfy URL**, which allows setting a [ntfy](https://ntfy.sh/) URL to which notifications of new detections will be sent. The topic should be unique to your device, e.g., `https://ntfy.sh/rayhunter_notifications_ba9di7ie` or `https://myserver.example.com/rayhunter_notifications_ba9di7ie`. The ntfy Android and iOS apps can then be used to receive notifications. More information can be found in the [ntfy docs](https://docs.ntfy.sh/).
- **Enabled Notification Types** allows enabling or disabling the following types of notifications:
- *Warnings*, which will alert when a heuristic is triggered. Alerts will be sent at most once every five minutes.
- *Low Battery*, which will alert when the device's battery is low. Notifications may not be supported for all devices—you can check if your device is supported by looking at whether the battery level indicator is functioning on the System Information section of the Rayhunter UI.
- With **Analyzer Heuristic Settings** you can switch on or off built-in [Rayhunter heuristics](heuristics.md). Some heuristics are experimental or can trigger a lot of false positive warnings in some networks (our tests have shown that some heuristics have different behaviour in US or European networks). In that case you can decide whether you would like to have the heuristics that trigger a lot of false positives on or off. Please note that we are constantly improving and adding new heuristics, so new release may reduce false positives in existing heuristics as well.
If you prefer editing `config.toml` file, you need to obtain a shell on your [Orbic](./orbic.md#obtaining-a-shell) or [TP-Link](./tplink-m7350.md#obtaining-a-shell) device and edit the file manually. You can view the [default configuration file on a GitHub](https://github.com/EFForg/rayhunter/blob/main/dist/config.toml.in).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 61 KiB