Initial flake for IP-Checker-GUI

This commit is contained in:
mia 2025-04-22 15:27:09 +02:00
commit 58b9af97da
8 changed files with 1976 additions and 0 deletions

1507
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

13
Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "IP-Checker-GUI"
version = "0.1.0"
edition = "2021"
[dependencies]
gtk4 = "0.8.1"
relm4 = "0.8.1"
tracker = "0.2.1"
chrono = "0.4.34"
relm4-components = "0.8.1"
relm4-icons = "0.8.2"
rand = "0.9.0-alpha.1"

21
README.md Normal file
View file

@ -0,0 +1,21 @@
# IP-Checker-GUI
IP Checker GUI is a simple tool for teachers that can make their students input the corresponding Network Address, Subnet Mask, and more to test their skill.
## Installation
1. Clone the repository
```bash
git clone https://github.com/TimTom2016/IP-Checker-GUI.git
```
2. Install gtk4
https://gtk-rs.org/gtk4-rs/stable/latest/book/installation.html
4. Install Rust
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
4. Run the program
```bash
cargo run --release
```

61
flake.lock generated Normal file
View file

@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1735563628,
"narHash": "sha256-OnSAY7XDSx7CtDoqNh8jwVwh4xNL/2HaJxGjryLWzX8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b134951a4c9f3c995fd7be05f3243f8ecd65d798",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

37
flake.nix Normal file
View file

@ -0,0 +1,37 @@
{
description = "IP-Checker-GUI packaged with Nix flake";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; # or unstable
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
};
rustPlatform = pkgs.rustPlatform;
in {
packages.default = rustPlatform.buildRustPackage {
pname = "ip-checker-gui";
version = "0.1.0";
src = pkgs.fetchFromGitHub {
owner = "TimTom2016";
repo = "IP-Checker-GUI";
rev = "master"; # or pin a commit
sha256 = "sha256-FO8jNXkiRQz/jDEydVjCH6O1qGSxnKUJhTnOOINpOMg="; # replace with actual hash
};
nativeBuildInputs = [ pkgs.pkg-config ];
buildInputs = [ pkgs.gtk4 ];
cargoLock = {
lockFile = ./Cargo.lock;
};
};
});
}

6
icons.toml Normal file
View file

@ -0,0 +1,6 @@
# Recommended: Specify your app ID *OR* your base resource path for more robust icon loading
#app_id = "relm4.test.simple"
# List of icon names you found (shipped with this crate)
# Note: the file ending `-symbolic.svg` isn't part of the icon name.
icons = ["cross", "check-plain"]

1
result Symbolic link
View file

@ -0,0 +1 @@
/nix/store/kgdgzmadqwzhr0agaf9ncchq0gmz0q0k-ip-checker-gui-0.1.0

330
src/main.rs Normal file
View file

@ -0,0 +1,330 @@
use gtk4::prelude::{EditableExt, EntryExt};
use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt};
use rand::{Rng, thread_rng};
use relm4::{ComponentParts, ComponentSender, gtk, RelmApp, RelmWidgetExt, SimpleComponent};
use relm4_icons::icon_names;
struct AppModel {
ip: Option<IpModel>,
user_input: IpModel,
valid: Validator,
}
#[derive(Debug, Clone)]
enum EntryInput {
Mask(String),
NetworkAddress(String),
BroadcastAddress(String),
FirstHost(String),
LastHost(String),
PossibleHosts(u32),
}
#[derive(Debug, Default, Clone)]
struct IpModel {
ip: String,
subnet_mask: u8,
prefix: String,
mask: String,
binary_address: String,
network_address: String,
broadcast_address: String,
first_host: String,
last_host: String,
possible_hosts: u32,
}
#[derive(Debug, Default)]
struct Validator {
mask: bool,
network_address: bool,
broadcast_address: bool,
first_host: bool,
last_host: bool,
possible_hosts: bool,
}
#[derive(Debug)]
enum AppMsg {
GenerateIp,
CheckIp,
EntryInput(EntryInput),
}
#[relm4::component]
impl SimpleComponent for AppModel {
type Input = AppMsg;
type Output = ();
type Init = Option<IpModel>;
view! {
gtk::Window {
set_title: Some("IP Checker"),
set_default_width: 300,
set_default_height: 100,
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 5,
set_margin_all: 5,
gtk::Button {
set_label: "Generate IP",
connect_clicked => AppMsg::GenerateIp,
},
#[name="ip_label"]
gtk::Label {
#[watch]
set_label: format!("IP: {}{}", model.ip.as_ref().map(|ip| &ip.ip).unwrap_or(&"Press Generate IP to get an IP".to_string()),model.ip.as_ref().map(|ip| &ip.prefix).unwrap_or(&"".to_string())).as_str(),
set_margin_all: 5,
},
// Input Fields
gtk::Entry {
set_placeholder_text: Some("Enter Subnet Mask"),
#[watch]
set_secondary_icon_name: if model.valid.mask { Some(icon_names::CHECK_PLAIN) } else { Some(icon_names::CROSS) },
set_margin_all: 5,
connect_changed[sender] => move |entry| {
let text = entry.text();
let subnet_mask = text.to_string();
sender.input(AppMsg::EntryInput(EntryInput::Mask(subnet_mask)));
},
},
gtk::Entry {
set_placeholder_text: Some("Network Address"),
#[watch]
set_secondary_icon_name: if model.valid.network_address { Some(icon_names::CHECK_PLAIN) } else { Some(icon_names::CROSS) },
set_margin_all: 5,
connect_changed[sender] => move |entry| {
let text = entry.text();
let ip = text.to_string();
sender.input(AppMsg::EntryInput(EntryInput::NetworkAddress(ip)));
},
},
gtk::Entry {
set_placeholder_text: Some("Broadcast Address"),
#[watch]
set_secondary_icon_name: if model.valid.broadcast_address { Some(icon_names::CHECK_PLAIN) } else { Some(icon_names::CROSS) },
set_margin_all: 5,
connect_changed[sender] => move |entry| {
let text = entry.text();
let broadcast = text.to_string();
sender.input(AppMsg::EntryInput(EntryInput::BroadcastAddress(broadcast)));
},
},
gtk::Entry {
set_placeholder_text: Some("First Host"),
#[watch]
set_secondary_icon_name: if model.valid.first_host { Some(icon_names::CHECK_PLAIN) } else { Some(icon_names::CROSS) },
set_margin_all: 5,
connect_changed[sender] => move |entry| {
let text = entry.text();
let first = text.to_string();
sender.input(AppMsg::EntryInput(EntryInput::FirstHost(first)));
},
},
gtk::Entry {
set_placeholder_text: Some("Last Host"),
#[watch]
set_secondary_icon_name: if model.valid.last_host { Some(icon_names::CHECK_PLAIN) } else { Some(icon_names::CROSS) },
set_margin_all: 5,
connect_changed[sender] => move |entry| {
let text = entry.text();
let last = text.to_string();
sender.input(AppMsg::EntryInput(EntryInput::LastHost(last)));
},
},
gtk::Entry {
set_placeholder_text: Some("Possible Hosts"),
#[watch]
set_secondary_icon_name: if model.valid.possible_hosts { Some(icon_names::CHECK_PLAIN) } else { Some(icon_names::CROSS) },
set_margin_all: 5,
connect_changed[sender] => move |entry| {
let text = entry.text();
let possible = text.to_string();
sender.input(AppMsg::EntryInput(EntryInput::PossibleHosts(possible.parse().unwrap())));
},
},
gtk::Button {
set_label: "Check IP",
connect_clicked[sender] => move |_| {
sender.input(AppMsg::CheckIp);
},
},
}
}
}
// Initialize the UI.
fn init(
ip: Self::Init,
root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let model = AppModel { ip, user_input: IpModel::default(), valid: Validator::default() };
// Insert the macro code generation here
let widgets = view_output!();
ComponentParts { model, widgets }
}
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
match msg {
AppMsg::GenerateIp => {
self.ip = Some(ip_calculations());
}
AppMsg::EntryInput(input) => {
match input {
EntryInput::Mask(mask) => {
self.user_input.mask = mask;
}
EntryInput::NetworkAddress(ip) => {
self.user_input.ip = ip;
}
EntryInput::BroadcastAddress(broadcast) => {
self.user_input.broadcast_address = broadcast;
}
EntryInput::FirstHost(first) => {
self.user_input.first_host = first;
}
EntryInput::LastHost(last) => {
self.user_input.last_host = last;
}
EntryInput::PossibleHosts(possible) => {
self.user_input.possible_hosts = possible;
}
}
}
AppMsg::CheckIp => {
self.valid = Validator {
mask: self.ip.as_ref().map(|ip| &ip.mask).unwrap_or(&"".to_string()) == &self.user_input.mask,
network_address: self.ip.as_ref().map(|ip| &ip.network_address).unwrap_or(&"".to_string()) == &self.user_input.ip,
broadcast_address: self.ip.as_ref().map(|ip| &ip.broadcast_address).unwrap_or(&"".to_string()) == &self.user_input.broadcast_address,
first_host: self.ip.as_ref().map(|ip| &ip.first_host).unwrap_or(&"".to_string()) == &self.user_input.first_host,
last_host: self.ip.as_ref().map(|ip| &ip.last_host).unwrap_or(&"".to_string()) == &self.user_input.last_host,
possible_hosts: self.ip.as_ref().map(|ip| &ip.possible_hosts).unwrap_or(&0) == &self.user_input.possible_hosts,
};
}
}
}
}
fn ip_calculations() -> IpModel {
let mut rng = thread_rng();
let octets: [u8; 4] = [
rng.gen_range(111..255),
rng.gen_range(111..255),
rng.gen_range(111..255),
rng.gen_range(111..255),
];
let subnet_mask: u8 = rng.gen_range(18..28);
let prefix = format!("\\{}", subnet_mask);
let ip = format!("{}.{}.{}.{}", octets[0], octets[1], octets[2], octets[3]);
let mut binary_mask = "1".repeat(subnet_mask as usize) + &"0".repeat(32 - subnet_mask as usize);
binary_mask = add_dots(&binary_mask);
let binary_address = format!("{:08b}.{:08b}.{:08b}.{:08b}", octets[0], octets[1], octets[2], octets[3]);
// calculate network address
let mut network_address = String::new();
for (i, c) in binary_address.chars().enumerate() {
if c == '.' {
network_address.push('.');
} else {
network_address.push_str(&((c.to_digit(10).unwrap() & binary_mask.chars().nth(i).unwrap().to_digit(10).unwrap()).to_string()));
}
}
// calculate broadcast address
let mut broadcast_address = String::new();
for (i, c) in binary_address.chars().enumerate() {
if i >= subnet_mask as usize {
if c == '.' {
broadcast_address.push('.');
} else {
broadcast_address.push('1');
}
} else {
broadcast_address.push(c);
}
}
// calculate broadcast address
let mut broadcast_address = String::new();
for (i, c) in network_address.chars().enumerate() {
if i >= (subnet_mask + subnet_mask / 8) as usize {
if c == '.' {
broadcast_address.push('.');
} else {
broadcast_address.push('1');
}
} else {
broadcast_address.push(c);
}
}
// calculate first host address
let mut first_host = network_address.clone();
let first_host_int = u32::from_str_radix(&first_host.replace(".", ""), 2).unwrap() + 1;
first_host = format!("{:032b}", first_host_int);
first_host = add_dots(&first_host);
// calculate last host address
let mut last_host = broadcast_address.clone();
let last_host_int = u32::from_str_radix(&last_host.replace(".", ""), 2).unwrap() - 1;
last_host = format!("{:032b}", last_host_int);
last_host = add_dots(&last_host);
broadcast_address = binary_dotted_to_decimal(&broadcast_address);
first_host = binary_dotted_to_decimal(&first_host);
binary_mask = binary_dotted_to_decimal(&binary_mask);
last_host = binary_dotted_to_decimal(&last_host);
network_address = binary_dotted_to_decimal(&network_address);
// calculate number of possible hosts
let number_of_hosts = if subnet_mask < 31 { 2u32.pow(32 - subnet_mask as u32) - 2 } else { 0 };
let model = IpModel {
ip,
subnet_mask,
prefix,
mask: binary_mask,
binary_address,
network_address,
broadcast_address,
first_host,
last_host,
possible_hosts: number_of_hosts,
};
return model;
}
fn add_dots(binary_string: &str) -> String {
let mut result = String::new();
for (i, c) in binary_string.chars().enumerate() {
if i % 8 == 0 && i != 0 {
result.push('.');
}
result.push(c);
}
result
}
fn binary_dotted_to_decimal(binary_dotted: &str) -> String {
let mut result = String::new();
for octet in binary_dotted.split('.') {
result.push_str(&u8::from_str_radix(octet, 2).unwrap().to_string());
result.push('.');
}
result.pop();
result
}
fn main() {
let app = RelmApp::new("timtom2016.com.IPChecker");
relm4_icons::initialize_icons();
app.run::<AppModel>(Some(ip_calculations()));
}