2
0
mirror of https://github.com/Revertron/Alfis synced 2024-11-03 15:40:19 +00:00

Reworked handling appropriate (mined) keys absence. Now that info handled in UI as well. It won't allow users without keys to mine domains or zones.

This commit is contained in:
Revertron 2021-03-23 18:55:11 +01:00
parent 046c06beeb
commit f5949e6ec0
11 changed files with 184 additions and 97 deletions

View File

@ -1,8 +1,8 @@
# Settings
origin = "00000102C2F9BFD2803284D93327F089D60FC72A06F19AF2384567F2646B8348"
key_file = "default.key"
key_file = "default1.key"
listen = "[::]:4244"
public = false
public = true
# Bootstrap nodes
#peers = ["test-ip4.alfis.name:4244", "test-ip6.alfis.name:4244"]

View File

@ -6,7 +6,7 @@ use log::{trace, debug, info, warn, error};
pub struct Context {
pub app_version: String,
pub settings: Settings,
pub keystore: Keystore,
pub keystore: Option<Keystore>,
pub chain: Chain,
pub x_zones: ExternalZones,
pub bus: Bus<Event>,
@ -14,7 +14,7 @@ pub struct Context {
impl Context {
/// Creating an essential context to work with
pub fn new(app_version: String, settings: Settings, keystore: Keystore, chain: Chain) -> Context {
pub fn new(app_version: String, settings: Settings, keystore: Option<Keystore>, chain: Chain) -> Context {
Context { app_version, settings, keystore, chain, x_zones: ExternalZones::new(), bus: Bus::new() }
}
@ -26,17 +26,17 @@ impl Context {
warn!("Error loading keystore '{}'!", filename);
},
Some(keystore) => {
self.keystore = keystore;
self.keystore = Some(keystore);
},
}
self
}
pub fn get_keystore(&self) -> Keystore {
pub fn get_keystore(&self) -> Option<Keystore> {
self.keystore.clone()
}
pub fn set_keystore(&mut self, keystore: Keystore) {
pub fn set_keystore(&mut self, keystore: Option<Keystore>) {
self.keystore = keystore;
}

View File

@ -244,8 +244,14 @@ impl DnsServer for DnsUdpServer {
let mut req_buffer = BytePacketBuffer::new();
let (_, src) = match socket.recv_from(&mut req_buffer.buf) {
Ok(x) => x,
Err(e) => {
debug!("Failed to read from UDP socket: {:?}", e);
Err(err) => {
if let Some(code) = err.raw_os_error() {
if code == 10004 {
debug!("UDP service loop has finished");
break;
}
}
debug!("Failed to read from UDP socket: {:?}", err);
continue;
}
};
@ -347,6 +353,12 @@ impl DnsServer for DnsTcpServer {
let stream = match wrap_stream {
Ok(stream) => stream,
Err(err) => {
if let Some(code) = err.raw_os_error() {
if code == 10004 {
debug!("TCP service loop has finished");
break;
}
}
warn!("Failed to accept TCP connection: {:?}", err);
continue;
}

View File

@ -1,4 +1,4 @@
use crate::Bytes;
use crate::{Bytes, Keystore};
#[derive(Clone, PartialEq, Debug)]
pub enum Event {
@ -12,7 +12,8 @@ pub enum Event {
NewBlockReceived,
BlockchainChanged { index: u64 },
ActionStopMining,
ActionMineLocker { index: u64, hash: Bytes },
ActionMineLocker { index: u64, hash: Bytes, keystore: Box<Keystore> },
ActionQuit,
NetworkStatus { nodes: usize, blocks: u64 },
Syncing { have: u64, height: u64 },
SyncFinished,

View File

@ -128,6 +128,12 @@ impl Clone for Keystore {
}
}
impl PartialEq for Keystore {
fn eq(&self, other: &Self) -> bool {
self.keypair.to_bytes().eq(&other.keypair.to_bytes())
}
}
/// Checks if some public key is "strong" enough to mine domains
/// TODO Optimize by caching Blakeout somewhere
pub fn check_public_key_strength(key: &Bytes, strength: usize) -> bool {
@ -155,7 +161,7 @@ pub fn create_key(context: Arc<Mutex<Context>>) {
let hash = keystore.get_hash().to_string();
info!("Key mined successfully: {:?}, hash: {}", &keystore.get_public(), &hash);
context.bus.post(Event::KeyCreated { path: keystore.get_path().to_owned(), public: keystore.get_public().to_string(), hash });
context.set_keystore(keystore);
context.set_keystore(Some(keystore));
}
}
let miners = miners_count.fetch_sub(1, atomic::Ordering::SeqCst) - 1;

View File

@ -80,13 +80,7 @@ fn main() {
let settings = Settings::load(&config_name);
info!(target: LOG_TARGET_MAIN, "Loaded settings: {:?}", &settings);
let keystore: Keystore = match Keystore::from_file(&settings.key_file, "") {
None => {
warn!(target: LOG_TARGET_MAIN, "Generated temporary keystore. Please, generate full-privileged keys.");
Keystore::new()
}
Some(keystore) => { keystore }
};
let keystore = Keystore::from_file(&settings.key_file, "");
let chain: Chain = Chain::new(&settings);
if opt_matches.opt_present("l") {
for i in 1..(chain.height() + 1) {
@ -102,7 +96,8 @@ fn main() {
Some(block) => { trace!(target: LOG_TARGET_MAIN, "Loaded DB with origin {:?}", &block.hash); }
}
let settings_copy = settings.clone();
let context: Arc<Mutex<Context>> = Arc::new(Mutex::new(Context::new(env!("CARGO_PKG_VERSION").to_owned(), settings, keystore, chain)));
let context = Context::new(env!("CARGO_PKG_VERSION").to_owned(), settings, keystore, chain);
let context: Arc<Mutex<Context>> = Arc::new(Mutex::new(context));
dns_utils::start_dns_server(&context, &settings_copy);
let mut miner_obj = Miner::new(Arc::clone(&context));
@ -136,9 +131,11 @@ fn create_genesis_if_needed(context: &Arc<Mutex<Context>>, miner: &Arc<Mutex<Min
let last_block = context.get_chain().last_block();
let origin = context.settings.origin.clone();
if origin.eq("") && last_block.is_none() {
// If blockchain is empty, we are going to mine a Genesis block
let block = Block::new(None, context.get_keystore().get_public(), Bytes::default(), BLOCK_DIFFICULTY);
miner.lock().unwrap().add_block(block);
if let Some(keystore) = &context.keystore {
// If blockchain is empty, we are going to mine a Genesis block
let block = Block::new(None, context.get_keystore().unwrap().get_public(), Bytes::default(), BLOCK_DIFFICULTY);
miner.lock().unwrap().add_block(block, keystore.clone());
}
}
}

View File

@ -8,17 +8,18 @@ use chrono::Utc;
use log::{debug, error, info, trace, warn};
use num_cpus;
use crate::{Block, Bytes, Context};
use crate::{Block, Bytes, Context, Keystore};
use crate::commons::{CHAIN_VERSION, LOCKER_DIFFICULTY, KEYSTORE_DIFFICULTY};
use crate::blockchain::enums::BlockQuality;
use crate::blockchain::hash_utils::*;
use crate::keys::check_public_key_strength;
use crate::event::Event;
use blakeout::Blakeout;
use std::ops::Deref;
pub struct Miner {
context: Arc<Mutex<Context>>,
blocks: Arc<Mutex<Vec<Block>>>,
jobs: Arc<Mutex<Vec<MineJob>>>,
running: Arc<AtomicBool>,
mining: Arc<AtomicBool>,
cond_var: Arc<Condvar>
@ -28,15 +29,15 @@ impl Miner {
pub fn new(context: Arc<Mutex<Context>>) -> Self {
Miner {
context,
blocks: Arc::new(Mutex::new(Vec::new())),
jobs: Arc::new(Mutex::new(Vec::new())),
running: Arc::new(AtomicBool::new(false)),
mining: Arc::new(AtomicBool::new(false)),
cond_var: Arc::new(Condvar::new())
}
}
pub fn add_block(&mut self, block: Block) {
self.blocks.lock().unwrap().push(block);
pub fn add_block(&mut self, block: Block, keystore: Keystore) {
self.jobs.lock().unwrap().push(MineJob { block, keystore });
self.cond_var.notify_one();
}
@ -48,7 +49,7 @@ impl Miner {
pub fn start_mining_thread(&mut self) {
let context = Arc::clone(&self.context);
let blocks = self.blocks.clone();
let blocks = self.jobs.clone();
let running = self.running.clone();
let mining = self.mining.clone();
let cond_var = self.cond_var.clone();
@ -73,7 +74,7 @@ impl Miner {
}
});
let mining = self.mining.clone();
let blocks = self.blocks.clone();
let blocks = self.jobs.clone();
let cond_var = self.cond_var.clone();
self.context.lock().unwrap().bus.register(move |_uuid, e| {
match e {
@ -82,11 +83,11 @@ impl Miner {
Event::ActionStopMining => {
mining.store(false, Ordering::SeqCst);
}
Event::ActionMineLocker { index, hash } => {
Event::ActionMineLocker { index, hash, keystore } => {
if !mining.load(Ordering::SeqCst) {
let mut block = Block::new(None, Bytes::default(), hash, LOCKER_DIFFICULTY);
block.index = index;
blocks.lock().unwrap().push(block);
blocks.lock().unwrap().push(MineJob { block, keystore: keystore.deref().clone() });
cond_var.notify_all();
info!("Added a locker block to mine");
}
@ -101,16 +102,16 @@ impl Miner {
self.running.load(Ordering::Relaxed)
}
fn mine_internal(context: Arc<Mutex<Context>>, mut block: Block, mining: Arc<AtomicBool>) {
fn mine_internal(context: Arc<Mutex<Context>>, mut job: MineJob, mining: Arc<AtomicBool>) {
// Clear signature and hash just in case
block.signature = Bytes::default();
block.hash = Bytes::default();
block.version = CHAIN_VERSION;
job.block.signature = Bytes::default();
job.block.hash = Bytes::default();
job.block.version = CHAIN_VERSION;
// If this block needs to be a locker
if block.index > 0 && !block.prev_block_hash.is_empty() {
if job.block.index > 0 && !job.block.prev_block_hash.is_empty() {
info!("Mining locker block");
block.pub_key = context.lock().unwrap().keystore.get_public();
if !check_public_key_strength(&block.pub_key, KEYSTORE_DIFFICULTY) {
job.block.pub_key = job.keystore.get_public();
if !check_public_key_strength(&job.block.pub_key, KEYSTORE_DIFFICULTY) {
warn!("Can not mine block with weak public key!");
context.lock().unwrap().bus.post(Event::MinerStopped);
mining.store(false, Ordering::SeqCst);
@ -121,7 +122,7 @@ impl Miner {
Some(last_block) => {
info!("Last block found");
// If we were doing something else and got new block before we could mine this block
if last_block.index > block.index || last_block.hash != block.prev_block_hash {
if last_block.index > job.block.index || last_block.hash != job.block.prev_block_hash {
warn!("We missed block to lock");
context.lock().unwrap().bus.post(Event::MinerStopped);
mining.store(false, Ordering::SeqCst);
@ -130,8 +131,8 @@ impl Miner {
}
}
} else {
block.index = context.lock().unwrap().chain.height() + 1;
block.prev_block_hash = match context.lock().unwrap().chain.last_block() {
job.block.index = context.lock().unwrap().chain.height() + 1;
job.block.prev_block_hash = match context.lock().unwrap().chain.last_block() {
None => { Bytes::default() }
Some(block) => { block.hash }
};
@ -144,12 +145,12 @@ impl Miner {
debug!("Starting {} threads for mining", cpus);
for _cpu in 0..cpus {
let context = Arc::clone(&context);
let block = block.clone();
let job = job.clone();
let mining = Arc::clone(&mining);
let live_threads = Arc::clone(&live_threads);
thread::spawn(move || {
live_threads.fetch_add(1, Ordering::SeqCst);
match find_hash(Arc::clone(&context), block, Arc::clone(&mining)) {
match find_hash(Arc::clone(&context), job.block, Arc::clone(&mining)) {
None => {
debug!("Mining was cancelled");
let count = live_threads.fetch_sub(1, Ordering::SeqCst);
@ -162,7 +163,7 @@ impl Miner {
Some(mut block) => {
let index = block.index;
let mut context = context.lock().unwrap();
block.signature = Bytes::from_bytes(&context.keystore.sign(&block.as_bytes()));
block.signature = Bytes::from_bytes(&job.keystore.sign(&block.as_bytes()));
if context.chain.check_new_block(&block) != BlockQuality::Good {
warn!("Error adding mined block!");
if index == 0 {
@ -184,6 +185,12 @@ impl Miner {
}
}
#[derive(Clone)]
pub struct MineJob {
block: Block,
keystore: Keystore
}
fn find_hash(context: Arc<Mutex<Context>>, mut block: Block, running: Arc<AtomicBool>) -> Option<Block> {
let difficulty = block.difficulty as usize;
let full = block.transaction.is_some();

View File

@ -18,6 +18,7 @@ use std::collections::HashSet;
use crate::{Context, Block, p2p::Message, p2p::State, p2p::Peer, p2p::Peers, Bytes};
use crate::blockchain::enums::BlockQuality;
use crate::commons::CHAIN_VERSION;
use std::sync::atomic::{AtomicBool, Ordering};
const SERVER: Token = Token(0);
const POLL_TIMEOUT: Option<Duration> = Some(Duration::from_millis(3000));
@ -40,6 +41,9 @@ impl Network {
(c.settings.listen.clone(), c.settings.peers.clone())
};
let running = Arc::new(AtomicBool::new(true));
subscribe_to_bus(&mut self.context, Arc::clone(&running));
// Starting server socket
let addr = listen_addr.parse().expect("Error parsing listen address");
let mut server = TcpListener::bind(addr).expect("Can't bind to address");
@ -62,6 +66,9 @@ impl Network {
loop {
// Poll Mio for events, blocking until we get an event.
poll.poll(&mut events, POLL_TIMEOUT).expect("Error polling sockets");
if !running.load(Ordering::SeqCst) {
break;
}
// Process each event.
for event in events.iter() {
@ -125,11 +132,26 @@ impl Network {
peers.send_pings(poll.registry(), height, hash);
peers.connect_new_peers(poll.registry(), &mut unique_token);
}
info!("Network loop finished");
});
Ok(())
}
}
fn subscribe_to_bus(context: &mut Arc<Mutex<Context>>, running: Arc<AtomicBool>) {
use crate::event::Event;
context.lock().unwrap().bus.register(move |_uuid, e| {
match e {
Event::ActionQuit => {
running.store(false, Ordering::SeqCst);
return false;
}
_ => {}
}
true
});
}
fn handle_connection_event(context: Arc<Mutex<Context>>, peers: &mut Peers, registry: &Registry, event: &Event) -> bool {
if event.is_error() || (event.is_read_closed() && event.is_write_closed()) {
return false;
@ -415,16 +437,19 @@ fn handle_message(context: Arc<Mutex<Context>>, message: Message, peers: &mut Pe
fn mine_locker_block(context: Arc<Mutex<Context>>) {
let mut context = context.lock().unwrap();
if let Some(block) = context.chain.last_block() {
if block.index < context.chain.max_height() {
info!("No locker mining while syncing");
return;
}
let lockers: HashSet<Bytes> = context.chain.get_block_lockers(&block).into_iter().collect();
if lockers.contains(&context.keystore.get_public()) {
info!("We have an honor to mine locker block!");
context.bus.post(crate::event::Event::ActionMineLocker { index: block.index + 1, hash: block.hash });
} else if !lockers.is_empty() {
info!("Locker block must be mined by other nodes");
if let Some(keystore) = &context.keystore {
if block.index < context.chain.max_height() {
info!("No locker mining while syncing");
return;
}
let lockers: HashSet<Bytes> = context.chain.get_block_lockers(&block).into_iter().collect();
if lockers.contains(&keystore.get_public()) {
info!("We have an honor to mine locker block!");
let keystore = Box::new(keystore.clone());
context.bus.post(crate::event::Event::ActionMineLocker { index: block.index + 1, hash: block.hash, keystore });
} else if !lockers.is_empty() {
info!("Locker block must be mined by other nodes");
}
}
}
}

View File

@ -61,12 +61,13 @@ pub fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
.build()
.expect("Error building GUI");
run_interface_loop(&mut interface);
let mut context = Arc::clone(&context);
run_interface_loop(&mut context, &mut interface);
interface.exit();
}
/// Indefinitely loops through WebView steps
fn run_interface_loop(interface: &mut WebView<()>) {
fn run_interface_loop(context: &mut Arc<Mutex<Context>>, interface: &mut WebView<()>) {
// We use this ugly loop to lower CPU usage a lot.
// If we use .run() or only .step() in a loop without sleeps it will try
// to support 60FPS and uses more CPU than it should.
@ -76,6 +77,8 @@ fn run_interface_loop(interface: &mut WebView<()>) {
match interface.step() {
None => {
info!("Interface closed, exiting");
context.lock().unwrap().bus.post(Event::ActionQuit);
thread::sleep(Duration::from_millis(100));
break;
}
Some(result) => {
@ -101,8 +104,10 @@ fn action_check_zone(context: &Arc<Mutex<Context>>, web_view: &mut WebView<()>,
web_view.eval("zoneAvailable(false)").expect("Error evaluating!");
} else {
let c = context.lock().unwrap();
let available = c.get_chain().is_domain_available(&name, &c.get_keystore());
web_view.eval(&format!("zoneAvailable({})", available)).expect("Error evaluating!");
if let Some(keystore) = c.get_keystore() {
let available = c.get_chain().is_domain_available(&name, &keystore);
web_view.eval(&format!("zoneAvailable({})", available)).expect("Error evaluating!");
}
}
}
@ -114,24 +119,31 @@ fn action_check_record(web_view: &mut WebView<()>, data: String) {
}
fn action_check_domain(context: &Arc<Mutex<Context>>, web_view: &mut WebView<()>, name: String) {
let name = name.to_lowercase();
let c = context.lock().unwrap();
let available = c.get_chain().is_domain_available(&name, &c.get_keystore());
web_view.eval(&format!("domainAvailable({})", available)).expect("Error evaluating!");
if let Some(keystore) = c.get_keystore() {
let name = name.to_lowercase();
let available = c.get_chain().is_domain_available(&name, &keystore);
web_view.eval(&format!("domainAvailable({})", available)).expect("Error evaluating!");
}
}
fn action_save_key(context: &Arc<Mutex<Context>>) {
if context.lock().unwrap().get_keystore().is_none() {
return;
}
let result = tfd::save_file_dialog_with_filter("Save keys file", "", &["*.key"], "Key files (*.key)");
match result {
None => {}
Some(new_path) => {
let mut context = context.lock().unwrap();
let path = new_path.clone();
let public = context.keystore.get_public().to_string();
let hash = context.keystore.get_hash().to_string();
context.keystore.save(&new_path, "");
info!("Key file saved to {}", &path);
context.bus.post(Event::KeySaved { path, public, hash });
if let Some(mut keystore) = context.get_keystore() {
let public = keystore.get_public().to_string();
let hash = keystore.get_hash().to_string();
keystore.save(&new_path, "");
info!("Key file saved to {}", &path);
context.bus.post(Event::KeySaved { path, public, hash });
}
}
}
}
@ -152,7 +164,7 @@ fn action_load_key(context: &Arc<Mutex<Context>>, _web_view: &mut WebView<()>) {
let public = keystore.get_public().to_string();
let hash = keystore.get_hash().to_string();
c.bus.post(Event::KeyLoaded { path, public, hash });
c.set_keystore(keystore);
c.set_keystore(Some(keystore));
}
}
}
@ -213,23 +225,32 @@ fn action_loaded(context: &Arc<Mutex<Context>>, web_view: &mut WebView<()>) {
};
if !eval.is_empty() {
//debug!("Evaluating {}", &eval);
handle.dispatch(move |web_view| {
web_view.eval(&eval.replace("\\", "\\\\"))
}).expect("Error dispatching!");
}
true
});
let eval = format!("keystoreChanged('{}', '{}', '{}');", c.keystore.get_path(), &c.keystore.get_public().to_string(), &c.keystore.get_hash().to_string());
debug!("Evaluating {}", &eval);
web_view.eval(&eval.replace("\\", "\\\\")).expect("Error evaluating!");
if let Some(keystore) = c.get_keystore() {
let eval = format!("keystoreChanged('{}', '{}', '{}');",
keystore.get_path(),
&keystore.get_public().to_string(),
&keystore.get_hash().to_string());
//debug!("Evaluating {}", &eval);
web_view.eval(&eval.replace("\\", "\\\\")).expect("Error evaluating!");
}
}
fn action_create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, web_view: &mut WebView<()>, name: String, records: &String) {
debug!("Creating domain with records: {}", records);
let c = Arc::clone(&context);
let context = context.lock().unwrap();
let pub_key = context.keystore.get_public();
if context.get_keystore().is_none() {
show_warning(web_view, "You don't have keys loaded!\nLoad or mine the keys and try again.");
return;
}
let keystore = context.get_keystore().unwrap();
let pub_key = keystore.get_public();
match context.chain.can_mine_domain(&name, &records, &pub_key) {
MineResult::Fine => {
let zone = get_domain_zone(&name);
@ -237,7 +258,6 @@ fn action_create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>,
if let Ok(records) = serde_json::from_str::<Vec<DnsRecord>>(&records) {
let data = DomainData::new(zone.clone(), records);
let data = serde_json::to_string(&data).unwrap();
let keystore = context.keystore.clone();
std::mem::drop(context);
create_domain(c, miner, &name, &data, difficulty, &keystore);
let _ = web_view.eval("domainMiningStarted()");
@ -271,18 +291,23 @@ fn action_create_zone(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, we
let context = context.lock().unwrap();
(context.get_keystore(), context.chain.get_domain_transaction(&name))
};
match transaction {
None => {
create_domain(Arc::clone(&context), miner.clone(), &name, &data, ZONE_DIFFICULTY, &keystore);
}
Some(transaction) => {
if transaction.pub_key == keystore.get_public() {
if let Some(keystore) = keystore {
match transaction {
None => {
create_domain(Arc::clone(&context), miner.clone(), &name, &data, ZONE_DIFFICULTY, &keystore);
} else {
warn!("Tried to mine not owned domain!");
show_warning(web_view, "You cannot change domain that you don't own!");
}
Some(transaction) => {
if transaction.pub_key == keystore.get_public() {
create_domain(Arc::clone(&context), miner.clone(), &name, &data, ZONE_DIFFICULTY, &keystore);
} else {
warn!("Tried to mine not owned domain!");
show_warning(web_view, "You cannot change domain that you don't own!");
}
}
}
} else {
warn!("Can not mine without keys!");
show_warning(web_view, "You don't have keys loaded!\nLoad or mine the keys and try again.");
}
}
@ -303,7 +328,7 @@ fn create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, name: &
//let tags_vector: Vec<String> = tags.into().trim().split(",").map(|s| s.trim()).map(String::from).collect();
let transaction = Transaction::from_str(name, "dns".to_owned(), data.to_owned(), keystore.get_public().clone());
let block = Block::new(Some(transaction), keystore.get_public(), Bytes::default(), difficulty);
miner.lock().unwrap().add_block(block);
miner.lock().unwrap().add_block(block, keystore.clone());
}
#[derive(Deserialize)]

View File

@ -154,7 +154,7 @@
<div class="content is-hidden" id="key_load">
<div class="field">
<label class="label">Key path</label>
<p id="key_file_name">Key not saved</p>
<p id="key_file_name">Not loaded</p>
</div>
<div class="field">
@ -169,16 +169,16 @@
<br>
<div class="field is-grouped">
<div class="control">
<button class="button is-success" onclick="loadKey();">Load key</button>
</div>
<div class="control">
<button class="button is-warning" onclick="createKey();">Mine new key</button>
</div>
<div class="control">
<button class="button is-primary" onclick="saveKey();">Save key</button>
<button class="button is-light" onclick="loadKey();">Load key</button>
</div>
<div class="control">
<button id="save_key" class="button is-light" onclick="saveKey();" disabled>Save key</button>
</div>
</div>
</div>
@ -190,7 +190,7 @@
<div class="field">
<label class="label">Domain name</label>
<div class="control">
<input class="input" type="text" placeholder="example.ygg" id="new_domain" oninput="onDomainChange(this)">
<input class="input" type="text" placeholder="example.ygg" id="new_domain" oninput="onDomainChange(this)" disabled>
</div>
</div>
</div>
@ -198,7 +198,7 @@
<div class="field">
<label class="label">Domain tags (will be used for search)</label>
<div class="control">
<input class="input" type="text" placeholder="blog, community, friendship" id="new_domain_tags">
<input class="input" type="text" placeholder="blog, community, friendship" id="new_domain_tags" disabled>
</div>
</div>
</div>
@ -210,10 +210,10 @@
<div class="field is-grouped">
<div class="control">
<button class="button is-success" id="add_record_button" onclick="showNewRecordDialog();">Add record</button>
<button id="new_domain_button" class="button is-warning" onclick="createDomain();" disabled>Mine domain</button>
</div>
<div class="control">
<button class="button is-link" id="new_domain_button" onclick="createDomain();" disabled>Mine domain</button>
<button id="add_record_button" class="button is-light" onclick="showNewRecordDialog();" disabled>Add record</button>
</div>
</div>
</div>
@ -225,7 +225,7 @@
<div class="field">
<label class="label">Zone name</label>
<div class="control">
<input class="input" type="text" placeholder="ygg" id="new_zone" oninput="onZoneChange()">
<input class="input" type="text" placeholder="ygg" id="new_zone" oninput="onZoneChange()" disabled>
</div>
</div>
</div>
@ -233,7 +233,7 @@
<div class="field">
<label class="label">Difficulty (for all domains in zone)</label>
<div class="control">
<input class="input" type="number" placeholder="20" id="new_zone_difficulty" oninput="onZoneChange()">
<input class="input" type="number" placeholder="20" id="new_zone_difficulty" oninput="onZoneChange()" disabled>
</div>
</div>
</div>
@ -241,7 +241,7 @@
<div class="field is-grouped">
<div class="control">
<button class="button is-link" id="new_zone_button" onclick="createZone();" disabled>Mine zone</button>
<button class="button is-warning" id="new_zone_button" onclick="createZone();" disabled>Mine zone</button>
</div>
</div>
</div>

View File

@ -189,12 +189,15 @@ function onDomainChange(element) {
function domainAvailable(available) {
input = document.getElementById("new_domain");
button = document.getElementById("new_domain_button");
button2 = document.getElementById("add_record_button");
if (available) {
input.className = "input";
button.disabled = false
button2.disabled = false
} else {
input.className = "input is-danger";
button.disabled = true
button2.disabled = true
}
}
@ -309,4 +312,15 @@ function keystoreChanged(path, pub_key, hash) {
key_public_key.innerHTML = pub_key;
var key_public_hash = document.getElementById("key_public_hash");
key_public_hash.innerHTML = hash;
var save_key = document.getElementById("save_key");
save_key.disabled = false;
var new_domain = document.getElementById("new_domain");
new_domain.disabled = false;
var new_domain_tags = document.getElementById("new_domain_tags");
new_domain_tags.disabled = false;
var new_zone = document.getElementById("new_zone");
new_zone.disabled = false;
var new_zone_difficulty = document.getElementById("new_zone_difficulty");
new_zone_difficulty.disabled = false;
}