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