Adds config updates and preserves logs when routing (#10)
* adds mod doc, removes unnecessary copyright / license * removes config form * uses a relative config path and generates a config if none exists * adds state, adds `tracing` crate, refactors spawning zebrad and reading its output * updates `save_config` command to kill the zebrad child process, (which will cause the output reader task to exit as well), read the existing config, overwrite it with the new config, start the zebrad process, and start a new task for reading its output. * Sleeps for a shorter duration before emitting logs as events * adds a `read_config` command and a textarea for editing it in the UI * adds edit mode * Adds example data, fixes issue with updating * Adds button styles * adds some textarea styles * adds syntax highlighting for config preview with highlightjs * adds a loading state while Zebrad restarts * adds tracing logs to save_config command, and wait for zebrad to shutdown before restarting * moves log state up to App so it persists across route changes, moves wait during setup for webview to start to the async runtime so it doesn't block the setup fn from starting the webview. * Disallow resizing the config file textarea without resizing the window --------- Co-authored-by: Automated Release Test <release-tests-no-reply@zfnd.org>
This commit is contained in:
parent
b554d659c4
commit
49d8eb2bac
|
@ -1,177 +0,0 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
|
@ -17,10 +17,10 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modular-forms/solid": "^0.20.0",
|
||||
"@solidjs/router": "^0.12.4",
|
||||
"@tauri-apps/api": ">=2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-shell": ">=2.0.0-beta.0",
|
||||
"highlight.js": "^11.9.0",
|
||||
"solid-js": "^1.7.8",
|
||||
"solid-styled-components": "^0.28.5",
|
||||
"vite": "^5.1.5"
|
||||
|
|
|
@ -8,9 +8,6 @@ overrides:
|
|||
rollup: npm:@rollup/wasm-node
|
||||
|
||||
dependencies:
|
||||
'@modular-forms/solid':
|
||||
specifier: ^0.20.0
|
||||
version: 0.20.0(solid-js@1.8.15)
|
||||
'@solidjs/router':
|
||||
specifier: ^0.12.4
|
||||
version: 0.12.5(solid-js@1.8.15)
|
||||
|
@ -20,6 +17,9 @@ dependencies:
|
|||
'@tauri-apps/plugin-shell':
|
||||
specifier: '>=2.0.0-beta.0'
|
||||
version: 2.0.0-beta.1
|
||||
highlight.js:
|
||||
specifier: ^11.9.0
|
||||
version: 11.9.0
|
||||
solid-js:
|
||||
specifier: ^1.7.8
|
||||
version: 1.8.15
|
||||
|
@ -478,14 +478,6 @@ packages:
|
|||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
dev: true
|
||||
|
||||
/@modular-forms/solid@0.20.0(solid-js@1.8.15):
|
||||
resolution: {integrity: sha512-CyXoeo2cwNel2N753c+iDzA+4DgtBrLNAS87DY/aODFZG7QoY0A9YSxdUxRf0N5NDwkzmn1O0XqwMP+EAiOHbA==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.3.1
|
||||
dependencies:
|
||||
solid-js: 1.8.15
|
||||
dev: false
|
||||
|
||||
/@rollup/wasm-node@4.12.0:
|
||||
resolution: {integrity: sha512-sqy3+YvV/uWX6bPZOR5PlEdH6xyMPXoelllRQ/uZ13tzy9f4pXZTbajnoWN8IHHXwTNKPiLzsePLiDEVmkxMNw==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
|
@ -814,6 +806,11 @@ packages:
|
|||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/highlight.js@11.9.0:
|
||||
resolution: {integrity: sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dev: false
|
||||
|
||||
/html-entities@2.3.3:
|
||||
resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==}
|
||||
dev: true
|
||||
|
|
|
@ -4467,4 +4467,5 @@ dependencies = [
|
|||
"tauri-build",
|
||||
"tauri-plugin-shell",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
|
|
@ -16,6 +16,7 @@ tauri-plugin-shell = "2.0.0-beta"
|
|||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tokio = { version = "1.36.0", features = ["full", "tracing", "test-util"] }
|
||||
tracing = "0.1.40"
|
||||
|
||||
[features]
|
||||
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
//! Child process management
|
||||
|
||||
use std::{
|
||||
io::{BufRead, BufReader},
|
||||
path::PathBuf,
|
||||
process::{Child, Command, Stdio},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use tauri::{utils, AppHandle, Manager};
|
||||
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
|
||||
/// Zebrad Configuration Filename
|
||||
pub const CONFIG_FILE: &str = "zebrad.toml";
|
||||
|
||||
#[cfg(windows)]
|
||||
pub const ZEBRAD_COMMAND_NAME: &str = "zebrad.exe";
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub const ZEBRAD_COMMAND_NAME: &str = "zebrad";
|
||||
|
||||
pub fn zebrad_config_path() -> PathBuf {
|
||||
let exe_path =
|
||||
utils::platform::current_exe().expect("could not get path to current executable");
|
||||
|
||||
let exe_dir_path = exe_path
|
||||
.parent()
|
||||
.expect("could not get path to parent directory of executable");
|
||||
|
||||
exe_dir_path.join(CONFIG_FILE)
|
||||
}
|
||||
|
||||
pub fn zebrad_bin_path() -> PathBuf {
|
||||
let exe_path =
|
||||
utils::platform::current_exe().expect("could not get path to current executable");
|
||||
|
||||
let exe_dir_path = exe_path
|
||||
.parent()
|
||||
.expect("could not get path to parent directory of executable");
|
||||
|
||||
exe_dir_path.join(ZEBRAD_COMMAND_NAME)
|
||||
}
|
||||
|
||||
pub fn run_zebrad() -> (Child, Receiver<String>) {
|
||||
let zebrad_config_path = zebrad_config_path();
|
||||
let zebrad_path = zebrad_bin_path();
|
||||
|
||||
let zebrad_config_path_str = zebrad_config_path.display().to_string();
|
||||
let zebrad_path_str = zebrad_path.display().to_string();
|
||||
|
||||
// Generate a default config if there's no existing config file
|
||||
if !zebrad_config_path.exists() {
|
||||
Command::new(&zebrad_path_str)
|
||||
.args(["generate", "-o", &zebrad_config_path_str])
|
||||
.spawn()
|
||||
.expect("could not start zebrad to generate default config")
|
||||
.wait()
|
||||
.expect("error waiting for `zebrad generate` to exit");
|
||||
|
||||
assert!(
|
||||
zebrad_config_path.exists(),
|
||||
"config file should exist after `zebrad generate` has exited"
|
||||
);
|
||||
}
|
||||
|
||||
let mut zebrad_child = Command::new(zebrad_path_str)
|
||||
.args(["-c", &zebrad_config_path_str])
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("zebrad should be installed as a bundled binary and should start successfully");
|
||||
|
||||
let zebrad_stdout = zebrad_child
|
||||
.stdout
|
||||
.take()
|
||||
.expect("should have anonymous pipe");
|
||||
|
||||
// Spawn a task for reading output and sending it to a channel
|
||||
let (output_sender, output_receiver) = tokio::sync::mpsc::channel(100);
|
||||
let _output_reader_task_handle = tauri::async_runtime::spawn_blocking(move || {
|
||||
for line in BufReader::new(zebrad_stdout).lines() {
|
||||
// Ignore send errors for now
|
||||
if let Err(error) =
|
||||
output_sender.blocking_send(line.expect("zebrad logs should be valid UTF-8"))
|
||||
{
|
||||
tracing::warn!(
|
||||
?error,
|
||||
"zebrad output channel is closed before output terminated"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
(zebrad_child, output_receiver)
|
||||
}
|
||||
|
||||
pub fn spawn_logs_emitter(
|
||||
mut output_receiver: Receiver<String>,
|
||||
app_handle: AppHandle,
|
||||
should_wait_for_webview: bool,
|
||||
) {
|
||||
tauri::async_runtime::spawn(async move {
|
||||
// Wait for webview to start
|
||||
if should_wait_for_webview {
|
||||
tokio::time::sleep(Duration::from_secs(3)).await;
|
||||
}
|
||||
|
||||
// Exit the task once the channel is closed and empty.
|
||||
while let Some(output) = output_receiver.recv().await {
|
||||
if let Err(error) = app_handle.emit("log", output) {
|
||||
tracing::warn!(?error, "log could not be serialized to JSON");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
|
@ -3,61 +3,66 @@
|
|||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use std::{
|
||||
io::{BufRead, BufReader},
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
use std::{fs, time::Duration};
|
||||
|
||||
use tauri::{AppHandle, Manager, RunEvent};
|
||||
use child_process::{run_zebrad, spawn_logs_emitter, zebrad_config_path};
|
||||
use tauri::{ipc::InvokeError, AppHandle, Manager, RunEvent};
|
||||
|
||||
mod process;
|
||||
use process::relative_command_path;
|
||||
mod child_process;
|
||||
mod state;
|
||||
|
||||
use state::AppState;
|
||||
|
||||
// TODO: Add a command for updating the config and restarting `zebrad` child process
|
||||
#[tauri::command]
|
||||
fn save_config() {}
|
||||
async fn save_config(app_handle: AppHandle, new_config: String) -> Result<String, InvokeError> {
|
||||
tracing::info!("dropping and killing zebrad child process");
|
||||
app_handle.state::<AppState>().kill_zebrad_child();
|
||||
let zebrad_config_path = zebrad_config_path();
|
||||
|
||||
tracing::info!("reading old config");
|
||||
let old_config_contents = fs::read_to_string(&zebrad_config_path)
|
||||
.map_err(|err| format!("could not read existing config file, error: {err}"))?;
|
||||
|
||||
tracing::info!("writing new config");
|
||||
fs::write(zebrad_config_path, new_config)
|
||||
.map_err(|err| format!("could not write to config file, error: {err}"))?;
|
||||
|
||||
tracing::info!("waiting for old zebrad child process to shutdown");
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
|
||||
tracing::info!("starting new zebrad child process");
|
||||
let (zebrad_child, zebrad_output_receiver) = run_zebrad();
|
||||
|
||||
tracing::info!("started new zebrad child process, starting output reader task");
|
||||
app_handle
|
||||
.state::<AppState>()
|
||||
.insert_zebrad_child(zebrad_child);
|
||||
spawn_logs_emitter(zebrad_output_receiver, app_handle, false);
|
||||
|
||||
Ok(old_config_contents)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn read_config() -> Result<String, InvokeError> {
|
||||
Ok(fs::read_to_string(zebrad_config_path())
|
||||
.map_err(|err| format!("could not read existing config file, error: {err}"))?)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Spawn initial zebrad process
|
||||
let mut zebrad = Command::new(relative_command_path("zebrad").unwrap())
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("zebrad should be installed as a bundled binary and should start successfully");
|
||||
|
||||
// Spawn a task for reading output and sending it to a channel
|
||||
let (zebrad_log_sender, mut zebrad_log_receiver) = tokio::sync::mpsc::channel(100);
|
||||
let zebrad_stdout = zebrad.stdout.take().expect("should have anonymous pipe");
|
||||
|
||||
// TODO: Use a blocking tokio/async_runtime thread? The io is blocking (reading the child process output from stdio), so
|
||||
// it shouldn't use a green thread
|
||||
let _log_emitter_handle = std::thread::spawn(move || {
|
||||
for line in BufReader::new(zebrad_stdout).lines() {
|
||||
// Ignore send errors for now
|
||||
let _ =
|
||||
zebrad_log_sender.blocking_send(line.expect("zebrad logs should be valid UTF-8"));
|
||||
}
|
||||
});
|
||||
let (zebrad_child, zebrad_output_receiver) = run_zebrad();
|
||||
|
||||
tauri::Builder::default()
|
||||
.manage(AppState::new(zebrad_child))
|
||||
.setup(|app| {
|
||||
let app_handle = app.handle().clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
loop {
|
||||
if let Some(output) = zebrad_log_receiver.recv().await {
|
||||
app_handle.emit("log", output.clone()).unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
spawn_logs_emitter(zebrad_output_receiver, app.handle().clone(), true);
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![save_config])
|
||||
.invoke_handler(tauri::generate_handler![save_config, read_config])
|
||||
.build(tauri::generate_context!())
|
||||
.unwrap()
|
||||
.run(move |app_handle: &AppHandle, _event| {
|
||||
if let RunEvent::Exit = &_event {
|
||||
zebrad.kill().expect("could not kill zebrad process");
|
||||
app_handle.state::<AppState>().kill_zebrad_child();
|
||||
app_handle.exit(0);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// Copyright 2024 Zcash Foundation
|
||||
// This code is modified from the Tauri code.
|
||||
|
||||
use tauri::utils;
|
||||
|
||||
// See <https://docs.rs/tauri/latest/src/tauri/api/process/command.rs.html#137-145>
|
||||
pub fn relative_command_path(command: impl std::fmt::Display) -> Option<String> {
|
||||
let current_exe = utils::platform::current_exe();
|
||||
|
||||
let exe_path = match current_exe {
|
||||
Ok(exe_dir) => exe_dir,
|
||||
Err(err) => {
|
||||
println!("error getting path to current executable: {err}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let exe_dir = exe_path.parent()?.display();
|
||||
|
||||
#[cfg(windows)]
|
||||
let path = format!("{}\\{command}.exe", exe_dir);
|
||||
|
||||
#[cfg(not(windows))]
|
||||
let path = format!("{}/{command}", exe_dir);
|
||||
|
||||
Some(path)
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
use std::{process::Child, sync::Mutex};
|
||||
|
||||
pub struct AppState {
|
||||
zebrad_child: Mutex<Option<Child>>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new(zebrad_child: Child) -> Self {
|
||||
Self {
|
||||
zebrad_child: Mutex::new(Some(zebrad_child)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_zebrad_child(&self, new_zebrad_child: Child) {
|
||||
let mut zebrad_child_handle = self
|
||||
.zebrad_child
|
||||
.lock()
|
||||
.expect("could not get lock on zebrad child mutex");
|
||||
|
||||
*zebrad_child_handle = Some(new_zebrad_child);
|
||||
}
|
||||
|
||||
/// Drops and kills the `zebrad_child` child process, if any.
|
||||
///
|
||||
/// Returns true if there was a zebrad child process that's been killed and dropped, or
|
||||
/// returns false if there was no zebrad child process in the state.
|
||||
pub fn kill_zebrad_child(&self) -> bool {
|
||||
if let Some(mut zebrad_child) = self
|
||||
.zebrad_child
|
||||
.lock()
|
||||
.expect("could not get lock on zebrad_child mutex")
|
||||
.take()
|
||||
{
|
||||
zebrad_child
|
||||
.kill()
|
||||
.expect("could not kill zebrad child process");
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
56
src/App.tsx
56
src/App.tsx
|
@ -1,6 +1,13 @@
|
|||
import { Router, Route, RouteSectionProps, A, useMatch } from "@solidjs/router";
|
||||
import { css, styled } from "solid-styled-components";
|
||||
|
||||
import { listen, Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
|
||||
import { createSignal, onCleanup, onMount } from "solid-js";
|
||||
|
||||
import { EXAMPLE_LOGS } from "./tests/example_data";
|
||||
import { MAX_NUM_LOG_LINES } from "./constants";
|
||||
|
||||
import { NAVIGATION_BAR_HEIGHT } from "./constants";
|
||||
import Logs from "./pages/Logs";
|
||||
import Configuration from "./pages/Configure";
|
||||
|
@ -68,9 +75,56 @@ const AppContainer = ({ children }: RouteSectionProps) => (
|
|||
);
|
||||
|
||||
function App() {
|
||||
const is_tauri_app = window.hasOwnProperty("__TAURI_INTERNALS__");
|
||||
const [logs, set_logs] = createSignal<Array<string>>([]);
|
||||
|
||||
const is_at_bottom = () => {
|
||||
const y_bottom = Math.ceil(window.scrollY) + window.innerHeight;
|
||||
return y_bottom >= document.body.scrollHeight;
|
||||
};
|
||||
|
||||
const scroll_to_bottom = () => {
|
||||
window.scroll(0, document.body.scrollHeight);
|
||||
};
|
||||
|
||||
if (is_tauri_app) {
|
||||
let stop_listening: UnlistenFn;
|
||||
|
||||
onMount(async () => {
|
||||
stop_listening = await listen("log", (event: Event<string>) => {
|
||||
const was_at_bottom = is_at_bottom();
|
||||
|
||||
set_logs([...logs().slice(-MAX_NUM_LOG_LINES), event.payload]);
|
||||
|
||||
if (was_at_bottom) {
|
||||
scroll_to_bottom();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onCleanup(() => stop_listening());
|
||||
} else {
|
||||
let example_log_index = 0;
|
||||
setInterval(() => {
|
||||
const was_at_bottom = is_at_bottom();
|
||||
|
||||
set_logs([
|
||||
...logs().slice(-MAX_NUM_LOG_LINES),
|
||||
EXAMPLE_LOGS[example_log_index],
|
||||
]);
|
||||
|
||||
// TODO: check if it's the logs page? May be easier to do if this logic is moved to `AppContainer`.
|
||||
if (was_at_bottom) {
|
||||
scroll_to_bottom();
|
||||
}
|
||||
|
||||
example_log_index = (example_log_index + 1) % EXAMPLE_LOGS.length;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
return (
|
||||
<Router root={AppContainer}>
|
||||
<Route path="/" component={Logs} />
|
||||
<Route path="/" component={() => <Logs logs={logs} />} />
|
||||
<Route path="/configure" component={Configuration} />
|
||||
</Router>
|
||||
);
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
// Modified from <https://github.com/fabian-hiller/modular-forms/blob/main/playgrounds/solid/src/components/Checkbox.tsx>
|
||||
// Copyright (c) 2022 Fabian Hiller
|
||||
// Copyright 2024 Zcash Foundation
|
||||
|
||||
import { JSX, splitProps } from "solid-js";
|
||||
import { css } from "solid-styled-components";
|
||||
|
||||
type CheckboxProps = {
|
||||
ref: (element: HTMLInputElement) => void;
|
||||
name: string;
|
||||
value?: string;
|
||||
checked?: boolean;
|
||||
onInput: JSX.EventHandler<HTMLInputElement, InputEvent>;
|
||||
onChange: JSX.EventHandler<HTMLInputElement, Event>;
|
||||
onBlur: JSX.EventHandler<HTMLInputElement, FocusEvent>;
|
||||
required?: boolean;
|
||||
class?: string;
|
||||
label: string;
|
||||
error?: string;
|
||||
padding?: "none";
|
||||
};
|
||||
|
||||
/**
|
||||
* Checkbox that allows users to select an option. The label next to the
|
||||
* checkbox describes the selection option.
|
||||
*/
|
||||
export function Checkbox(props: CheckboxProps) {
|
||||
const [, inputProps] = splitProps(props, [
|
||||
"class",
|
||||
"value",
|
||||
"label",
|
||||
"error",
|
||||
"padding",
|
||||
]);
|
||||
return (
|
||||
<div style={{ margin: "16px 0" }}>
|
||||
<label
|
||||
class={css`
|
||||
border: solid 1px ${props.checked ? "#fff" : "#888"};
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
`}
|
||||
>
|
||||
<span
|
||||
class={css`
|
||||
padding 8px 16px;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
color: ${props.checked ? "#fff" : "#888"};
|
||||
`}
|
||||
>
|
||||
{props.label}:
|
||||
</span>
|
||||
<input
|
||||
{...inputProps}
|
||||
class={css`
|
||||
margin: auto 8px;
|
||||
width: 24px;
|
||||
`}
|
||||
type="checkbox"
|
||||
id={props.name}
|
||||
value={props.value || ""}
|
||||
checked={props.checked}
|
||||
aria-invalid={!!props.error}
|
||||
aria-errormessage={`${props.name}-error`}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
import { css, styled } from "solid-styled-components";
|
||||
import { createForm, getValue } from "@modular-forms/solid";
|
||||
import { Checkbox } from "../components/checkbox";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { Accessor, createSignal, onMount } from "solid-js";
|
||||
import { styled } from "solid-styled-components";
|
||||
|
||||
import hljs from "highlight.js";
|
||||
|
||||
import { EXAMPLE_CONFIG_CONTENTS } from "../tests/example_data";
|
||||
|
||||
const PageContainer = styled("div")`
|
||||
display: flex;
|
||||
|
@ -10,220 +14,138 @@ const PageContainer = styled("div")`
|
|||
font-family: sans-serif;
|
||||
`;
|
||||
|
||||
const SubmitButton = styled("button")`
|
||||
display: inline-block;
|
||||
margin: 16px 0 0;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
background: transparent;
|
||||
const FloatingButtonContainer = styled("div")`
|
||||
background: #1c1c1c;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
padding: 6px 8px 0 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: #1c1c1c 0 0 6px 2px, #1c1c1c 0 0 12px 2px, #1c1c1c 0 0 24px 2px;
|
||||
`;
|
||||
|
||||
const Button = styled("button")`
|
||||
outline: none;
|
||||
border: solid 2px white;
|
||||
color: white;
|
||||
border: solid 1px white;
|
||||
padding: 8px 14px;
|
||||
margin: 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
`;
|
||||
letter-spacing: 1px;
|
||||
background: transparent;
|
||||
|
||||
type ConsensusConfig = {
|
||||
checkpoint_sync: boolean;
|
||||
};
|
||||
|
||||
type NetworkConfig = {
|
||||
listen_addr: string;
|
||||
network: string;
|
||||
initial_mainnet_peers: string;
|
||||
initial_testnet_peers: string;
|
||||
cache_dir: { enabled: boolean; custom_path?: string };
|
||||
peerset_initial_target_size: number;
|
||||
crawl_new_peer_interval: number;
|
||||
max_connections_per_ip: number;
|
||||
};
|
||||
|
||||
// Note: Interfaces don't seem to work well with @modular-forms
|
||||
type ZebradConfig = {
|
||||
consensus: ConsensusConfig;
|
||||
network: NetworkConfig;
|
||||
};
|
||||
|
||||
const DEFAULT_INITIAL_VALUES = {
|
||||
consensus: { checkpoint_sync: true },
|
||||
network: {
|
||||
listen_addr: "0.0.0.0:8233",
|
||||
network: "Mainnet",
|
||||
initial_mainnet_peers: `"dnsseed.z.cash:8233","dnsseed.str4d.xyz:8233","mainnet.seeder.zfnd.org:8233","mainnet.is.yolo.money:8233",`,
|
||||
initial_testnet_peers: `"dnsseed.testnet.z.cash:18233","testnet.seeder.zfnd.org:18233","testnet.is.yolo.money:18233"`,
|
||||
cache_dir: {
|
||||
enabled: true,
|
||||
},
|
||||
peerset_initial_target_size: 25,
|
||||
crawl_new_peer_interval: 61000,
|
||||
max_connections_per_ip: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const TextFieldLabel = styled("label")`
|
||||
border: solid 1px #fff;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
margin: 16px 0 0;
|
||||
`;
|
||||
|
||||
const TextFieldLabelSpan = styled("span")`
|
||||
display: flex;
|
||||
padding: 8px 16px;
|
||||
color: #fff;
|
||||
`;
|
||||
|
||||
const TextFieldInput = styled("input")`
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: none;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
text-align: right;
|
||||
flex-grow: 1;
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
&:hover {
|
||||
color: #aaa;
|
||||
border-color: #aaa;
|
||||
}
|
||||
`;
|
||||
|
||||
const ConfigTextArea = styled("textarea")`
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
background: none;
|
||||
color: white;
|
||||
padding: 0 8px;
|
||||
resize: none;
|
||||
`;
|
||||
|
||||
const ConfigDisplay = ({ children }: { children: Accessor<string> }) => {
|
||||
const highlighted_code = () =>
|
||||
hljs.highlight(children(), {
|
||||
language: "toml",
|
||||
}).value;
|
||||
|
||||
return (
|
||||
<pre>
|
||||
<code innerHTML={highlighted_code()} />
|
||||
</pre>
|
||||
);
|
||||
};
|
||||
|
||||
const Configuration = () => {
|
||||
// TODO: Read in this initial value from Zebra's existing config file
|
||||
// (Generate the default config if none exists)
|
||||
const [config_store, { Form, Field }] = createForm<ZebradConfig>({
|
||||
initialValues: DEFAULT_INITIAL_VALUES,
|
||||
const is_tauri_app = window.hasOwnProperty("__TAURI_INTERNALS__");
|
||||
|
||||
const [config_contents, set_config_contents] = createSignal<string>("");
|
||||
const [edited_config, set_edited_config] = createSignal<string | null>(null);
|
||||
const [is_saving, set_is_saving] = createSignal<boolean>(false);
|
||||
|
||||
onMount(async () => {
|
||||
if (is_tauri_app) {
|
||||
set_config_contents(await invoke("read_config"));
|
||||
} else {
|
||||
set_config_contents(EXAMPLE_CONFIG_CONTENTS);
|
||||
}
|
||||
});
|
||||
|
||||
const save_and_apply = (values: ZebradConfig) => {
|
||||
console.log("save and apply");
|
||||
console.log(values);
|
||||
const save_and_apply = async () => {
|
||||
let new_config = edited_config();
|
||||
|
||||
if (new_config === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_config_contents(new_config);
|
||||
set_edited_config(null);
|
||||
set_is_saving(true);
|
||||
|
||||
if (is_tauri_app) {
|
||||
await invoke("save_config", { newConfig: new_config });
|
||||
} else {
|
||||
await new Promise((resolve) => setTimeout(resolve, 450));
|
||||
}
|
||||
|
||||
set_is_saving(false);
|
||||
};
|
||||
|
||||
const discard_changes = () => {
|
||||
set_edited_config(null);
|
||||
};
|
||||
|
||||
const start_editing = () => {
|
||||
set_edited_config(config_contents());
|
||||
};
|
||||
|
||||
const is_editable = () => edited_config() !== null;
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<h1>Configuration</h1>
|
||||
<div>
|
||||
<Form onSubmit={save_and_apply}>
|
||||
<h5>Consensus:</h5>
|
||||
|
||||
<Field name="consensus.checkpoint_sync" type="boolean">
|
||||
{(field, props) => (
|
||||
<Checkbox
|
||||
{...props}
|
||||
checked={field.value}
|
||||
error={field.error}
|
||||
label="Enable Checkpoint Sync"
|
||||
/>
|
||||
{is_editable() ? (
|
||||
<>
|
||||
<ConfigTextArea
|
||||
value={edited_config() || ""}
|
||||
onChange={({ currentTarget: { value } }) =>
|
||||
set_edited_config(value)
|
||||
}
|
||||
/>
|
||||
<FloatingButtonContainer>
|
||||
<Button onClick={discard_changes}>Discard Changes</Button>
|
||||
<Button onClick={save_and_apply}>Save & Apply</Button>
|
||||
</FloatingButtonContainer>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ConfigDisplay>{config_contents}</ConfigDisplay>
|
||||
<FloatingButtonContainer>
|
||||
{is_saving() ? (
|
||||
<span
|
||||
style={{
|
||||
padding: "16px",
|
||||
"margin-top": "16px",
|
||||
display: "inline-block",
|
||||
}}
|
||||
>
|
||||
Saving and restarting Zebra ...
|
||||
</span>
|
||||
) : (
|
||||
<Button onClick={start_editing}>Edit</Button>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<h5>Network:</h5>
|
||||
|
||||
<Field name="network.listen_addr" type="string">
|
||||
{(field, props) => (
|
||||
<div>
|
||||
<TextFieldLabel>
|
||||
<TextFieldLabelSpan class={css``}>
|
||||
Listen Address:
|
||||
</TextFieldLabelSpan>
|
||||
<TextFieldInput
|
||||
{...props}
|
||||
type="text"
|
||||
value={field.value}
|
||||
class={css``}
|
||||
/>
|
||||
</TextFieldLabel>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name="network.network" type="string">
|
||||
{(field, props) => (
|
||||
<div>
|
||||
<TextFieldLabel>
|
||||
<TextFieldLabelSpan class={css``}>
|
||||
Network Type (Mainnet/Testnet):
|
||||
</TextFieldLabelSpan>
|
||||
<TextFieldInput
|
||||
{...props}
|
||||
type="text"
|
||||
value={field.value}
|
||||
class={css``}
|
||||
/>
|
||||
</TextFieldLabel>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name="network.initial_mainnet_peers" type="string">
|
||||
{(field, props) => (
|
||||
<div>
|
||||
<TextFieldLabel>
|
||||
<TextFieldLabelSpan class={css``}>
|
||||
Initial Mainnet Peers:
|
||||
</TextFieldLabelSpan>
|
||||
<TextFieldInput
|
||||
{...props}
|
||||
type="text"
|
||||
value={field.value}
|
||||
class={css``}
|
||||
/>
|
||||
</TextFieldLabel>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name="network.initial_testnet_peers" type="string">
|
||||
{(field, props) => (
|
||||
<div>
|
||||
<TextFieldLabel>
|
||||
<TextFieldLabelSpan class={css``}>
|
||||
Initial Testnet Peers:
|
||||
</TextFieldLabelSpan>
|
||||
<TextFieldInput
|
||||
{...props}
|
||||
type="text"
|
||||
value={field.value}
|
||||
class={css``}
|
||||
/>
|
||||
</TextFieldLabel>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name="network.cache_dir.enabled" type="boolean">
|
||||
{(field, props) => (
|
||||
<Checkbox
|
||||
{...props}
|
||||
checked={field.value}
|
||||
error={field.error}
|
||||
label="Enable Initial Peer Caching"
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
{getValue(config_store, "network.cache_dir.enabled") ? (
|
||||
<Field name="network.cache_dir.custom_path" type="string">
|
||||
{(field, props) => (
|
||||
<div>
|
||||
<TextFieldLabel>
|
||||
<TextFieldLabelSpan class={css``}>
|
||||
Custom Initial Peer Cache Dir (optional):
|
||||
</TextFieldLabelSpan>
|
||||
<TextFieldInput
|
||||
{...props}
|
||||
type="text"
|
||||
value={field.value}
|
||||
class={css``}
|
||||
/>
|
||||
</TextFieldLabel>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
) : null}
|
||||
|
||||
<SubmitButton type="submit">Save & Apply</SubmitButton>
|
||||
</Form>
|
||||
</div>
|
||||
</FloatingButtonContainer>
|
||||
</>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
import { listen, Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
|
||||
import { createSignal, onCleanup, onMount, For } from "solid-js";
|
||||
import { For, Accessor } from "solid-js";
|
||||
import { styled } from "solid-styled-components";
|
||||
|
||||
import { EXAMPLE_LOGS } from "../tests/example_logs";
|
||||
import { MAX_NUM_LOG_LINES } from "../constants";
|
||||
|
||||
const LogContainer = styled("div")`
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
|
@ -68,53 +63,7 @@ const Log = ({ children }: { children: string }) => {
|
|||
}
|
||||
};
|
||||
|
||||
const Logs = () => {
|
||||
const is_tauri_app = window.hasOwnProperty("__TAURI_INTERNALS__");
|
||||
const [logs, set_logs] = createSignal<Array<string>>([]);
|
||||
|
||||
const is_at_bottom = () => {
|
||||
const y_bottom = Math.ceil(window.scrollY) + window.innerHeight;
|
||||
return y_bottom >= document.body.scrollHeight;
|
||||
};
|
||||
|
||||
const scroll_to_bottom = () => {
|
||||
window.scroll(0, document.body.scrollHeight);
|
||||
};
|
||||
|
||||
if (is_tauri_app) {
|
||||
let stop_listening: UnlistenFn;
|
||||
|
||||
onMount(async () => {
|
||||
stop_listening = await listen("log", (event: Event<string>) => {
|
||||
const was_at_bottom = is_at_bottom();
|
||||
|
||||
set_logs([...logs().slice(-MAX_NUM_LOG_LINES), event.payload]);
|
||||
|
||||
if (was_at_bottom) {
|
||||
scroll_to_bottom();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onCleanup(() => stop_listening());
|
||||
} else {
|
||||
let example_log_index = 0;
|
||||
setInterval(() => {
|
||||
const was_at_bottom = is_at_bottom();
|
||||
|
||||
set_logs([
|
||||
...logs().slice(-MAX_NUM_LOG_LINES),
|
||||
EXAMPLE_LOGS[example_log_index],
|
||||
]);
|
||||
|
||||
if (was_at_bottom) {
|
||||
scroll_to_bottom();
|
||||
}
|
||||
|
||||
example_log_index = (example_log_index + 1) % EXAMPLE_LOGS.length;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
const Logs = ({ logs }: { logs: Accessor<string[]> }) => {
|
||||
return (
|
||||
<LogContainer>
|
||||
<For each={logs()} fallback={<Log>Waiting for zebrad to start...</Log>}>
|
||||
|
|
|
@ -12,3 +12,24 @@ body {
|
|||
color: #f6f6f6;
|
||||
background-color: #2f2f2f;
|
||||
}
|
||||
|
||||
.hljs-comment {
|
||||
color: rgb(86, 174, 86);
|
||||
}
|
||||
|
||||
.hljs-section,
|
||||
.hljs-attr {
|
||||
color: rgb(151, 190, 229);
|
||||
}
|
||||
|
||||
.hljs-literal {
|
||||
color: rgb(87, 151, 215);
|
||||
}
|
||||
|
||||
.hljs-string {
|
||||
color: rgb(225, 143, 118);
|
||||
}
|
||||
|
||||
.hljs-number {
|
||||
color: rgb(183, 241, 171);
|
||||
}
|
||||
|
|
|
@ -23,3 +23,59 @@ export const EXAMPLE_LOGS = [
|
|||
`2024-02-29T20:01:26.275644Z INFO zebra_state::service::finalized_state::disk_db: the open file limit is high enough for Zebra current_limit=1048576 min_limit=512 ideal_limit=1024`,
|
||||
`2024-02-29T20:01:26.271188Z INFO zebrad::application: loaded zebrad config config_path=Some("/home/ar/.config/zebrad.toml") config=ZebradConfig { consensus: Config { checkpoint_sync: true }, metrics: Config { endpoint_addr: None }, network: Config { listen_addr: 0.0.0.0:8233, network: Mainnet, initial_mainnet_peers: {"dnsseed.z.cash:8233", "dnsseed.str4d.xyz:8233", "mainnet.seeder.zfnd.org:8233", "mainnet.is.yolo.money:8233"}, initial_testnet_peers: {"dnsseed.testnet.z.cash:18233", "testnet.seeder.zfnd.org:18233", "testnet.is.yolo.money:18233"}, cache_dir: IsEnabled(true), peerset_initial_target_size: 25, crawl_new_peer_interval: 61s, max_connections_per_ip: 1 }, state: Config { cache_dir: "/home/ar/.cache/zebra", ephemeral: false, delete_old_database: true, debug_stop_at_height: None, debug_validity_check_interval: None }, tracing: Config { inner: InnerConfig { use_color: true, force_use_color: false, filter: None, buffer_limit: 128000, endpoint_addr: None, flamegraph: None, progress_bar: None, log_file: None, use_journald: false } }, sync: Config { download_concurrency_limit: 50, checkpoint_verify_concurrency_limit: 1000, full_verify_concurrency_limit: 20, parallel_cpu_threads: 0 }, mempool: Config { tx_cost_limit: 80000000, eviction_memory_time: 3600s, debug_enable_at_height: None }, rpc: Config { listen_addr: None, parallel_cpu_threads: 0, debug_force_finished_sync: false }, mining: Config { miner_address: None, extra_coinbase_data: None, debug_like_zcashd: true }, shielded_scan: Config { sapling_keys_to_scan: 0, db_config: Config { cache_dir: "/home/ar/.cache/zebra-scan", ephemeral: false, delete_old_database: true, debug_stop_at_height: None, debug_validity_check_interval: None } } }`,
|
||||
];
|
||||
|
||||
export const EXAMPLE_CONFIG_CONTENTS = `
|
||||
# Default configuration for zebrad.
|
||||
|
||||
[consensus]
|
||||
checkpoint_sync = true
|
||||
|
||||
[mempool]
|
||||
eviction_memory_time = "1h"
|
||||
tx_cost_limit = 80000000
|
||||
|
||||
[metrics]
|
||||
|
||||
[mining]
|
||||
debug_like_zcashd = true
|
||||
|
||||
[network]
|
||||
cache_dir = true
|
||||
crawl_new_peer_interval = "1m 1s"
|
||||
initial_mainnet_peers = [
|
||||
"dnsseed.z.cash:8233",
|
||||
"dnsseed.str4d.xyz:8233",
|
||||
"mainnet.seeder.zfnd.org:8233",
|
||||
"mainnet.is.yolo.money:8233",
|
||||
]
|
||||
initial_testnet_peers = [
|
||||
"dnsseed.testnet.z.cash:18233",
|
||||
"testnet.seeder.zfnd.org:18233",
|
||||
"testnet.is.yolo.money:18233",
|
||||
]
|
||||
listen_addr = "0.0.0.0:8233"
|
||||
max_connections_per_ip = 1
|
||||
network = "Mainnet"
|
||||
peerset_initial_target_size = 25
|
||||
|
||||
[rpc]
|
||||
debug_force_finished_sync = false
|
||||
parallel_cpu_threads = 0
|
||||
|
||||
[state]
|
||||
cache_dir = "/home/ar/.cache/zebra"
|
||||
delete_old_database = true
|
||||
ephemeral = false
|
||||
|
||||
[sync]
|
||||
checkpoint_verify_concurrency_limit = 1000
|
||||
download_concurrency_limit = 50
|
||||
full_verify_concurrency_limit = 20
|
||||
parallel_cpu_threads = 0
|
||||
|
||||
[tracing]
|
||||
buffer_limit = 128000
|
||||
force_use_color = false
|
||||
use_color = true
|
||||
use_journald = false
|
||||
`;
|
Loading…
Reference in New Issue