From 10a6813a8b58b8ca189858dea55d48fbeee92b38 Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Wed, 1 Oct 2025 22:57:48 +0300 Subject: [PATCH] wip --- src/ui/credentails.rs | 2 +- src/ui/domains.rs | 242 ++++++++++++++++++++++++++++++++++++++++++ src/ui/mod.rs | 11 +- 3 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 src/ui/domains.rs diff --git a/src/ui/credentails.rs b/src/ui/credentails.rs index d8a5f11..9256054 100644 --- a/src/ui/credentails.rs +++ b/src/ui/credentails.rs @@ -19,7 +19,7 @@ use gtk::{ }; -pub fn menu_credentials(window: &ApplicationWindow, parent: >k::Box, context: &Arc>) { +pub fn menu(window: &ApplicationWindow, parent: >k::Box, context: &Arc>) { let key_data = Rc::new(RefCell::from(KeyData{hash: String::new(), path: String::new(), public: String::new()})); let keys = Box::new(Orientation::Horizontal, 8); keys.set_hexpand(true); diff --git a/src/ui/domains.rs b/src/ui/domains.rs new file mode 100644 index 0000000..5897983 --- /dev/null +++ b/src/ui/domains.rs @@ -0,0 +1,242 @@ +use std::{ + sync::{Arc, MutexGuard}, + rc::Rc, cell::RefCell +}; + +use alfis::{ + blockchain::{ + types::MineResult, + transaction::DomainData + }, + crypto::CryptoBox, + Keystore, + Block, + Bytes, + Transaction, + from_hex, + is_yggdrasil_record, + CLASS_DOMAIN, + DOMAIN_LIFETIME, + DOMAIN_DIFFICULTY, + MAX_RECORDS +}; + +use crate::{Mutex, Context, Miner, ui::KeyData}; +use log::{debug, error, info, warn}; +use chrono::Utc; + +use gtk::{ + prelude::*, + AlertDialog, + ApplicationWindow, + Box, + Button, + Entry, + FileDialog, + Label, + Orientation, + Overlay, + Popover +}; + + +struct Domain<'a> { + domain: &'a str, + timestamp: u32, + d: &'a str, +} + + +pub fn menu(app: &adw::Application, window: &ApplicationWindow, parent: >k::Box, context: &Arc>, miner: &Arc>) { + // let new_domain_btn = Button::with_label("New domain"); + let new_domain_btn = Button::from_icon_name("list-add"); + new_domain_btn.add_css_class("suggested-action"); + new_domain_btn.set_halign(gtk::Align::End); + new_domain_btn.set_hexpand(false); + parent.append(&new_domain_btn); + + // let new_domain_child = Box::new(Orientation::Vertical, 16); + + // let new_domain_desc = Box::new(Orientation::Horizontal, 8); + // new_domain_child.append(&new_domain_desc); + + // let domain_entry = Entry::builder() + // .placeholder_text("example") + // .hexpand(true) + // .build(); + // new_domain_desc.append(&domain_entry); + + // let new_domain_pop = Popover::builder() + // .autohide(true) + // .child(&new_domain_child) + // .build(); + // parent.append(&new_domain_pop); + + // let _ = new_domain_btn.connect_clicked(move |_| { new_domain_pop.popup(); }); + + // let win = ApplicationWindow::builder() + // .application(app) + // .title("Мое модальное окно") + // .default_width(400) + // .default_height(300) + // .modal(true) // Это делает окно модальным + // .build(); + + // win.set_child(Some(&Label::new(Some("Meow")))); + + // // Устанавливаем родительское окно (допустим, `parent_window` - это ссылка на главное окно) + // parent.append(&win); + // // let tmp_modal = AlertDialog::builder() + // // .message("This is the test message") + // // .modal(true) + // // .build(); + // let tmp_layout = Overlay::builder() + // .child(&Label::new(Some("This is the test message"))) + // .build(); + // parent.append(&tmp_layout); + // // tmp_modal.show(Some(window)); +} + +fn load_domains(context: &mut MutexGuard) { + // let _ = handle.dispatch(move |web_view|{ + // web_view.eval("clearMyDomains();") + // }); + let domains = context.chain.get_my_domains(context.get_keystore()); + let mut domains = domains.iter().map(|(_, d)| d).collect::>(); + domains.sort_by(|a, b| a.0.cmp(&b.0)); + for (domain, timestamp, data) in domains { + let d = serde_json::to_string(&data).unwrap(); + let d = d.replace("'", "\\'").replace("\\n", "\\\\n").replace("\"", "\\\""); + let command = format!("addMyDomain('{}', {}, {}, '{}');", &domain, timestamp, timestamp + DOMAIN_LIFETIME, &d); + // let _ = handle.dispatch(move |web_view|{ + // web_view.eval(&command) + // }); + } + // let _ = handle.dispatch(move |web_view|{ + // web_view.eval("refreshMyDomains();") + // }); +} + +fn action_create_domain( + context: Arc>, + miner: Arc>, + name: String, + data: String, + signing: String, + encryption: String, + renewal: bool +) { + debug!("Creating domain with data: {}", &data); + let c = Arc::clone(&context); + let context = context.lock().unwrap(); + if !context.has_keys() { + // show_warning(web_view, "You don't have keys loaded!
Load or mine the keys and try again."); + // let _ = web_view.eval("domainMiningUnavailable();"); + return; + } + if context.chain.is_waiting_signers() { + // show_warning(web_view, "Waiting for last full block to be signed. Try again later."); + // let _ = web_view.eval("domainMiningUnavailable();"); + info!("Waiting for last full block to be signed. Try again later."); + return; + } + let keystore = context.get_keystore().unwrap().clone(); + let pub_key = keystore.get_public(); + let data = match serde_json::from_str::(&data) { + Ok(data) => data, + Err(e) => { + // show_warning(web_view, "Something wrong with domain data. I cannot mine it."); + // let _ = web_view.eval("domainMiningUnavailable();"); + warn!("Error parsing data: {}", e); + return; + } + }; + info!("Parsed domain data:\n{:#?}", &data); + if data.records.len() > MAX_RECORDS { + // show_warning(web_view, "Too many records. Mining more than 30 records not allowed."); + // let _ = web_view.eval("domainMiningUnavailable();"); + return; + } + // Check if yggdrasil only quality of zone is not violated + let zones = context.chain.get_zones(); + for z in zones { + if z.name == data.zone && z.yggdrasil { + for record in &data.records { + if !is_yggdrasil_record(record) { + // show_warning(web_view, &format!("Zone {} is Yggdrasil only, you cannot use IPs from clearnet!", &data.zone)); + // let _ = web_view.eval("domainMiningUnavailable();"); + return; + } + } + } + } + let (signing, encryption) = if signing.is_empty() || encryption.is_empty() { + (keystore.get_public(), keystore.get_encryption_public()) + } else { + (Bytes::new(from_hex(&signing).unwrap()), Bytes::new(from_hex(&encryption).unwrap())) + }; + match context.chain.can_mine_domain(context.chain.get_height(), &name, &pub_key) { + MineResult::Fine => { + drop(context); + create_domain(c, miner, CLASS_DOMAIN, &name, data, DOMAIN_DIFFICULTY, &keystore, signing, encryption, renewal); + // let _ = web_view.eval("domainMiningStarted();"); + // event_info(web_view, &format!("Mining of domain \\'{}\\' has started", &name)); + } + MineResult::WrongName => { + // show_warning(web_view, "You can't mine this domain!"); + // let _ = web_view.eval("domainMiningUnavailable();"); + } + MineResult::WrongData => { + // show_warning(web_view, "You have an error in records!"); + // let _ = web_view.eval("domainMiningUnavailable();"); + } + MineResult::WrongKey => { + // show_warning(web_view, "You can't mine with current key!"); + // let _ = web_view.eval("domainMiningUnavailable();"); + } + MineResult::WrongZone => { + // show_warning(web_view, "You can't mine domain in this zone!"); + // let _ = web_view.eval("domainMiningUnavailable();"); + } + MineResult::NotOwned => { + // show_warning(web_view, "This domain is already taken, and it is not yours!"); + // let _ = web_view.eval("domainMiningUnavailable();"); + } + MineResult::Cooldown { time } => { + // event_info(web_view, &format!("You have cooldown {}!", format_cooldown(time))); + // show_warning(web_view, &format!("You have cooldown {}!", format_cooldown(time))); + // let _ = web_view.eval("domainMiningUnavailable();"); + } + } +} + +fn create_domain( + context: Arc>, + miner: Arc>, + class: &str, + name: &str, + mut data: DomainData, + difficulty: u32, + keystore: &Keystore, + signing: Bytes, + encryption: Bytes, + renewal: bool +) { + let name = name.to_owned(); + let encrypted = CryptoBox::encrypt(encryption.as_slice(), name.as_bytes()).expect("Error encrypting domain name!"); + data.encrypted = Bytes::from_bytes(&encrypted); + + let data = serde_json::to_string(&data).unwrap(); + let (signing, encryption) = if signing.is_empty() || encryption.is_empty() { + (keystore.get_public(), keystore.get_encryption_public()) + } else { + (signing, encryption) + }; + let transaction = Transaction::from_str(name, class.to_owned(), data, signing, encryption); + // If this domain is already in blockchain we approve slightly smaller difficulty + let height = context.lock().unwrap().chain.get_height(); + let discount = context.lock().unwrap().chain.get_identity_discount(&transaction.identity, renewal, height, Utc::now().timestamp()); + let block = Block::new(Some(transaction), keystore.get_public(), Bytes::default(), difficulty - discount); + miner.lock().unwrap().add_block(block, keystore.clone()); +} + diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 885d0de..ac158ca 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,5 +1,6 @@ #[warn(unused_imports)] mod credentails; +mod domains; use alfis::{ blockchain::{ @@ -52,6 +53,7 @@ pub fn run_ui(context: Arc>, miner: Arc>) -> glib::E .build(); let context = context.clone(); + let miner = miner.clone(); application.connect_activate(move |app| { let window = ApplicationWindow::builder() .application(app) @@ -66,11 +68,14 @@ pub fn run_ui(context: Arc>, miner: Arc>) -> glib::E let nb = Notebook::new(); window.set_child(Some(&nb)); - let main = Box::new(Orientation::Vertical, 5); - credentails::menu_credentials(&window, &main, &context); + let credentails = Box::new(Orientation::Vertical, 4); + credentails::menu(&window, &credentails, &context); + let domains = Box::new(Orientation::Vertical, 4); + domains::menu(app, &window, &domains, &context, &miner); - nb.append_page(&main, Some(&Label::new(Some("Credentials")))); + nb.append_page(&credentails, Some(&Label::new(Some("Credentials")))); + nb.append_page(&domains, Some(&Label::new(Some("Domains")))); window.present(); });