Initial flake for IP-Checker-GUI
This commit is contained in:
commit
58b9af97da
8 changed files with 1976 additions and 0 deletions
1507
Cargo.lock
generated
Normal file
1507
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
13
Cargo.toml
Normal file
13
Cargo.toml
Normal 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
21
README.md
Normal 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
61
flake.lock
generated
Normal 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
37
flake.nix
Normal 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
6
icons.toml
Normal 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
1
result
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
/nix/store/kgdgzmadqwzhr0agaf9ncchq0gmz0q0k-ip-checker-gui-0.1.0
|
330
src/main.rs
Normal file
330
src/main.rs
Normal 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()));
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue