Compare commits
5 Commits
071567fa73
...
feat/gui
| Author | SHA1 | Date | |
|---|---|---|---|
| 10a6813a8b | |||
| 886f42e062 | |||
| f06e6f9121 | |||
| 6240208c17 | |||
| 9b1dc672a9 |
Generated
+459
-206
File diff suppressed because it is too large
Load Diff
+7
-4
@@ -42,7 +42,9 @@ spmc = "0.3.0"
|
|||||||
thread-priority = "1.2.0"
|
thread-priority = "1.2.0"
|
||||||
|
|
||||||
# Optional dependencies regulated by features
|
# Optional dependencies regulated by features
|
||||||
web-view = { git = "https://github.com/Boscop/web-view", features = [], optional = true }
|
# web-view = { git = "https://github.com/Boscop/web-view", features = [], optional = true }
|
||||||
|
gtk = { package = "gtk4", version = "0.9.6", features = ["v4_16"], optional = true }
|
||||||
|
adw = { version = "0.7.2", package = "libadwaita", features = ["v1_5"], optional = true }
|
||||||
tinyfiledialogs = { version = "3.9.1", optional = true }
|
tinyfiledialogs = { version = "3.9.1", optional = true }
|
||||||
open = { version = "5.3.0", optional = true }
|
open = { version = "5.3.0", optional = true }
|
||||||
|
|
||||||
@@ -69,7 +71,8 @@ ProductName="ALFIS"
|
|||||||
FileDescription="Alternative Free Identity System"
|
FileDescription="Alternative Free Identity System"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
webgui = ["web-view", "tinyfiledialogs", "open"]
|
gui = ["gtk", "adw"]
|
||||||
edge = ["webgui", "web-view/edge"]
|
# webgui = ["web-view", "tinyfiledialogs", "open"]
|
||||||
|
# edge = ["webgui", "web-view/edge"]
|
||||||
doh = ["ureq"]
|
doh = ["ureq"]
|
||||||
default = ["webgui", "doh"]
|
default = ["gui", "doh"]
|
||||||
|
|||||||
Binary file not shown.
@@ -1,6 +1,13 @@
|
|||||||
{ pkgs ? import <nixpkgs> { } }:
|
{ pkgs ? import <nixpkgs> { } }:
|
||||||
|
|
||||||
pkgs.mkShell {
|
pkgs.mkShell {
|
||||||
buildInputs =
|
buildInputs = with pkgs; [
|
||||||
[ pkgs.cargo pkgs.rustc pkgs.webkitgtk pkgs.pkg-config pkgs.kdialog ];
|
cargo
|
||||||
|
rustc
|
||||||
|
rust-analyzer
|
||||||
|
pkg-config
|
||||||
|
libsForQt5.kdialog
|
||||||
|
libadwaita
|
||||||
|
gtk4
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-5
@@ -29,8 +29,9 @@ use alfis::{dns_utils, Block, Bytes, Chain, Context, Keystore, Miner, Network, S
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use crate::win_service::start_service;
|
use crate::win_service::start_service;
|
||||||
|
|
||||||
#[cfg(feature = "webgui")]
|
// #[cfg(feature = "webgui")]
|
||||||
mod web_ui;
|
// mod web_ui;
|
||||||
|
mod ui;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
mod win_service;
|
mod win_service;
|
||||||
|
|
||||||
@@ -142,7 +143,7 @@ fn main() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut no_gui = opt_matches.opt_present("n");
|
let mut no_gui = opt_matches.opt_present("n");
|
||||||
if !cfg!(feature = "webgui")
|
if !cfg!(feature = "gui")
|
||||||
{
|
{
|
||||||
no_gui = true;
|
no_gui = true;
|
||||||
}
|
}
|
||||||
@@ -250,8 +251,9 @@ fn main() {
|
|||||||
post(Event::Error { text: String::from("Error starting DNS-server. Please, check that it’s port is not busy.") });
|
post(Event::Error { text: String::from("Error starting DNS-server. Please, check that it’s port is not busy.") });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
#[cfg(feature = "webgui")]
|
// #[cfg(feature = "webgui")]
|
||||||
web_ui::run_interface(Arc::clone(&context), miner);
|
// web_ui::run_interface(Arc::clone(&context), miner);
|
||||||
|
ui::run_ui(Arc::clone(&context), miner);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Without explicitly detaching the console cmd won't redraw it's prompt.
|
// Without explicitly detaching the console cmd won't redraw it's prompt.
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
use std::{
|
||||||
|
sync::Arc,
|
||||||
|
rc::Rc, cell::RefCell
|
||||||
|
};
|
||||||
|
|
||||||
|
use alfis::Keystore;
|
||||||
|
use crate::{Mutex, Context, ui::KeyData};
|
||||||
|
use log::{error, info, warn};
|
||||||
|
|
||||||
|
use gtk::{
|
||||||
|
prelude::*,
|
||||||
|
ApplicationWindow,
|
||||||
|
Button,
|
||||||
|
Label,
|
||||||
|
Box,
|
||||||
|
Orientation,
|
||||||
|
Entry,
|
||||||
|
FileDialog
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
pub fn menu(window: &ApplicationWindow, parent: >k::Box, context: &Arc<Mutex<Context>>) {
|
||||||
|
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);
|
||||||
|
parent.append(&keys);
|
||||||
|
|
||||||
|
let key_hash = Entry::builder()
|
||||||
|
.placeholder_text("No key loaded")
|
||||||
|
.editable(false)
|
||||||
|
.tooltip_text("If you load or mine a keypair the public key will be displayed here")
|
||||||
|
.hexpand(true)
|
||||||
|
.focusable(false)
|
||||||
|
.build();
|
||||||
|
keys.append(&key_hash);
|
||||||
|
|
||||||
|
let load_btn = Button::with_label("Load key");
|
||||||
|
|
||||||
|
let window = window.clone();
|
||||||
|
let context = context.clone();
|
||||||
|
load_btn.connect_clicked(move |_| {
|
||||||
|
open_file(&window, &key_hash, &context, key_data.clone());
|
||||||
|
});
|
||||||
|
keys.append(&load_btn);
|
||||||
|
|
||||||
|
let save_btn = Button::builder()
|
||||||
|
.label("Save key")
|
||||||
|
.sensitive(false)
|
||||||
|
.build();
|
||||||
|
keys.append(&save_btn);
|
||||||
|
|
||||||
|
let mine_btn = Button::with_label("Mine new key");
|
||||||
|
mine_btn.add_css_class("suggested-action");
|
||||||
|
keys.append(&mine_btn);
|
||||||
|
|
||||||
|
parent.append(&Label::new(Some("To mine domains you need a strong pair of signing keys and a pair of ecryption keys.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_file(window: &ApplicationWindow, key_hash: &Entry ,context: &Arc<Mutex<Context>>, keydata: Rc<RefCell<KeyData>>) {
|
||||||
|
let filter = gtk::FileFilter::new();
|
||||||
|
filter.add_pattern("*.key");
|
||||||
|
filter.add_pattern("*.toml");
|
||||||
|
filter.set_name(Some("Key files"));
|
||||||
|
|
||||||
|
let fc = FileDialog::builder()
|
||||||
|
.title("Select key")
|
||||||
|
.default_filter(&filter)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let context = context.clone();
|
||||||
|
let key_hash = key_hash.clone();
|
||||||
|
fc.open(Some(window), None::<gtk::gio::Cancellable>.as_ref(), move |result| {
|
||||||
|
match result {
|
||||||
|
Ok(file) => {
|
||||||
|
match Keystore::from_file(file.path().unwrap().to_str().unwrap(), "") {
|
||||||
|
None => {
|
||||||
|
error!("Error loading keystore '{}'!", &file.path().unwrap().to_str().unwrap());
|
||||||
|
// show_warning(web_view, "Error loading key!<br>Key cannot be loaded or its difficulty is not enough."); # TODO: display warnings
|
||||||
|
// event_fail(web_view, &format!("Error loading key from \\'{}\\'!", &file_name));
|
||||||
|
}
|
||||||
|
Some(keystore) => {
|
||||||
|
info!("Loaded keystore with keys: {:?}, {:?}", &keystore.get_public(), &keystore.get_encryption_public());
|
||||||
|
|
||||||
|
let path = keystore.get_path().to_owned();
|
||||||
|
let public = keystore.get_public().to_string();
|
||||||
|
let hash = keystore.get_hash().to_string();
|
||||||
|
let _ = &key_hash.set_text(public.as_str());
|
||||||
|
|
||||||
|
let key_data = KeyData { path, public, hash };
|
||||||
|
*keydata.borrow_mut() = key_data;
|
||||||
|
|
||||||
|
if !context.lock().unwrap().select_key_by_public(&keystore.get_public()) {
|
||||||
|
context.lock().unwrap().add_keystore(keystore);
|
||||||
|
} else {
|
||||||
|
warn!("This key is already loaded!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
if !e.matches(gtk::gio::IOErrorEnum::Cancelled) {
|
||||||
|
error!("Error selecting file: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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<Mutex<Context>>, miner: &Arc<Mutex<Miner>>) {
|
||||||
|
// 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<Context>) {
|
||||||
|
// 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::<Vec<_>>();
|
||||||
|
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<Mutex<Context>>,
|
||||||
|
miner: Arc<Mutex<Miner>>,
|
||||||
|
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!<br>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::<DomainData>(&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<Mutex<Context>>,
|
||||||
|
miner: Arc<Mutex<Miner>>,
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#[warn(unused_imports)]
|
||||||
|
mod credentails;
|
||||||
|
mod domains;
|
||||||
|
|
||||||
|
use alfis::{
|
||||||
|
blockchain::{
|
||||||
|
transaction::DomainData,
|
||||||
|
types::MineResult
|
||||||
|
},
|
||||||
|
commons::*,
|
||||||
|
crypto::CryptoBox,
|
||||||
|
dns::protocol::DnsRecord,
|
||||||
|
event::Event,
|
||||||
|
eventbus::{post, register},
|
||||||
|
miner::Miner,
|
||||||
|
keystore, Block, Bytes, Context, Keystore, Transaction
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
sync::{Arc, Mutex, MutexGuard},
|
||||||
|
thread,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
rc::Rc, cell::RefCell
|
||||||
|
};
|
||||||
|
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::{
|
||||||
|
glib,
|
||||||
|
ApplicationWindow,
|
||||||
|
Button,
|
||||||
|
Label,
|
||||||
|
HeaderBar,
|
||||||
|
Box,
|
||||||
|
Orientation,
|
||||||
|
Notebook,
|
||||||
|
Entry,
|
||||||
|
FileDialog
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct KeyData {
|
||||||
|
pub path: String,
|
||||||
|
pub public: String,
|
||||||
|
pub hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[warn(unused_variables)]
|
||||||
|
pub fn run_ui(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>) -> glib::ExitCode {
|
||||||
|
let application = adw::Application::builder()
|
||||||
|
.application_id("ru.Revertron.Alfis")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let context = context.clone();
|
||||||
|
let miner = miner.clone();
|
||||||
|
application.connect_activate(move |app| {
|
||||||
|
let window = ApplicationWindow::builder()
|
||||||
|
.application(app)
|
||||||
|
.title(format!("ALFIS {}", env!("CARGO_PKG_VERSION")))
|
||||||
|
.default_width(1023)
|
||||||
|
.default_height(720)
|
||||||
|
.resizable(false)
|
||||||
|
.build();
|
||||||
|
let ab = HeaderBar::new();
|
||||||
|
window.set_titlebar(Some(&ab));
|
||||||
|
|
||||||
|
let nb = Notebook::new();
|
||||||
|
window.set_child(Some(&nb));
|
||||||
|
|
||||||
|
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(&credentails, Some(&Label::new(Some("Credentials"))));
|
||||||
|
nb.append_page(&domains, Some(&Label::new(Some("Domains"))));
|
||||||
|
|
||||||
|
window.present();
|
||||||
|
});
|
||||||
|
|
||||||
|
application.run()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user