This commit is contained in:
dr-frmr 2024-10-15 16:46:21 -04:00
parent 4521d82093
commit 5486100e1d
No known key found for this signature in database
7 changed files with 186 additions and 690 deletions

70
Cargo.lock generated
View File

@ -78,7 +78,7 @@ name = "alias"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"serde",
"serde_json",
"wit-bindgen",
@ -1012,7 +1012,7 @@ dependencies = [
"alloy-sol-types",
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"process_macros",
"rand 0.8.5",
"serde",
@ -1383,7 +1383,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"serde",
"serde_json",
"url",
@ -1592,7 +1592,7 @@ name = "cat"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"serde",
"serde_json",
"wit-bindgen",
@ -1656,7 +1656,7 @@ dependencies = [
"alloy-sol-types",
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"process_macros",
"rand 0.8.5",
"serde",
@ -1675,7 +1675,7 @@ version = "0.2.1"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"pleco",
"serde",
"serde_json",
@ -1867,7 +1867,7 @@ checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
name = "contacts"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"serde",
"serde_json",
"wit-bindgen",
@ -2443,7 +2443,7 @@ name = "download"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"process_macros",
"serde",
"serde_json",
@ -2455,7 +2455,7 @@ name = "downloads"
version = "0.5.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"process_macros",
"rand 0.8.5",
"serde",
@ -2492,7 +2492,7 @@ dependencies = [
name = "echo"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"wit-bindgen",
]
@ -2731,7 +2731,7 @@ version = "0.2.0"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"process_macros",
"rand 0.8.5",
"serde",
@ -2885,7 +2885,7 @@ dependencies = [
name = "get_block"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"serde",
"serde_json",
"wit-bindgen",
@ -2950,7 +2950,7 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
name = "globe"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"serde",
"serde_json",
"url",
@ -3077,7 +3077,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
name = "help"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"wit-bindgen",
]
@ -3106,7 +3106,7 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
name = "hi"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"serde",
"serde_json",
"wit-bindgen",
@ -3139,7 +3139,7 @@ version = "0.1.1"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"serde",
"serde_json",
"wit-bindgen",
@ -3454,7 +3454,7 @@ name = "install"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"process_macros",
"serde",
"serde_json",
@ -3631,7 +3631,7 @@ name = "kfetch"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"rmp-serde",
"serde",
"serde_json",
@ -3643,7 +3643,7 @@ name = "kill"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"serde",
"serde_json",
"wit-bindgen",
@ -3715,9 +3715,8 @@ dependencies = [
[[package]]
name = "kinode_process_lib"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76c5b69ac1fc0cb457c7714ceb8c0a5bdbee4ee00b837f9f16ea711e902bdfe8"
version = "0.9.2"
source = "git+https://github.com/kinode-dao/process_lib.git?rev=9ac9e51#9ac9e513c0228f2dcfe8999ed4ca2c38246ee3db"
dependencies = [
"alloy 0.1.4",
"alloy-primitives",
@ -3738,8 +3737,9 @@ dependencies = [
[[package]]
name = "kinode_process_lib"
version = "0.9.2"
source = "git+https://github.com/kinode-dao/process_lib.git?rev=9ac9e51#9ac9e513c0228f2dcfe8999ed4ca2c38246ee3db"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7722aef4bff0625445fafda89a02f82ce0e16c7def6024e1317ae55a632ad331"
dependencies = [
"alloy 0.1.4",
"alloy-primitives",
@ -3840,7 +3840,7 @@ dependencies = [
"alloy-sol-types",
"anyhow",
"hex",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"rmp-serde",
"serde",
"serde_json",
@ -4067,7 +4067,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"regex",
"serde",
"serde_json",
@ -4237,7 +4237,7 @@ dependencies = [
name = "net_diagnostics"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"rmp-serde",
"serde",
"wit-bindgen",
@ -4563,7 +4563,7 @@ dependencies = [
name = "peer"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"rmp-serde",
"serde",
"wit-bindgen",
@ -4573,7 +4573,7 @@ dependencies = [
name = "peers"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"rmp-serde",
"serde",
"wit-bindgen",
@ -5614,7 +5614,7 @@ dependencies = [
"anyhow",
"base64 0.22.1",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"rmp-serde",
"serde",
"serde_json",
@ -5832,7 +5832,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
name = "state"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"serde",
"serde_json",
"wit-bindgen",
@ -6009,7 +6009,7 @@ version = "0.1.1"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"rand 0.8.5",
"regex",
"serde",
@ -6023,7 +6023,7 @@ version = "0.1.1"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"process_macros",
"serde",
"serde_json",
@ -6280,7 +6280,7 @@ version = "0.2.0"
dependencies = [
"anyhow",
"clap",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"serde",
"serde_json",
"wit-bindgen",
@ -6611,7 +6611,7 @@ name = "uninstall"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.3",
"process_macros",
"serde",
"serde_json",

View File

@ -0,0 +1,35 @@
interface contacts {
enum capabilities {
read-name-only,
read,
add,
remove,
}
variant contacts-request {
get-names, // requires read-names-only
get-all-contacts, // requires read
get-contact(string), // requires read
add-contact(string), // requires add
// tuple<node, field, value>
add-field(tuple<string, string, string>), // requires add
remove-contact(string), // requires remove
// tuple<node, field>
remove-field(tuple<string, string>), // requires remove
}
variant contacts-response {
get-names(list<string>),
get-all-contacts, // JSON all-contacts dict in blob
get-contact, // JSON contact dict in blob
add-contact,
add-field,
remove-contact,
remove-field,
}
}
world contacts-sys-v0 {
import contacts;
include process-v0;
}

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -1,40 +1,85 @@
use crate::kinode::process::contacts::{ContactsRequest, ContactsResponse};
use kinode_process_lib::{
await_message, call_init, eth, get_blob, homepage, http, kernel_types, net, println, Address,
LazyLoadBlob, Message, NodeId, ProcessId, Request, Response, SendError, SendErrorKind,
await_message, call_init, get_blob, get_typed_state, homepage, http, println, set_state,
Address, LazyLoadBlob, Message, NodeId, Response,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
const ICON: &str = include_str!("icon");
#[derive(Debug, Serialize, Deserialize)]
struct Contact(HashMap<String, serde_json::Value>);
#[derive(Debug, Serialize, Deserialize)]
struct Contacts(HashMap<NodeId, Contact>);
#[derive(Debug, Serialize, Deserialize)]
struct ContactsState {
our: Address,
contacts: Contacts,
}
impl ContactsState {
fn new(our: Address) -> Self {
Self { our }
get_typed_state(|bytes| serde_json::from_slice(bytes)).unwrap_or(Self {
our,
contacts: Contacts(HashMap::new()),
})
}
fn save(&self) {
set_state(&serde_json::to_vec(&self).expect("Failed to serialize contacts state!"));
}
fn contacts(&self) -> &Contacts {
&self.contacts
}
fn get_contact(&self, node: NodeId) -> Option<&Contact> {
self.contacts.0.get(&node)
}
fn add_contact(&mut self, node: NodeId) {
self.contacts.0.insert(node, Contact(HashMap::new()));
}
fn remove_contact(&mut self, node: NodeId) {
self.contacts.0.remove(&node);
}
fn add_field(&mut self, node: NodeId, field: String, value: serde_json::Value) {
self.contacts
.0
.entry(node)
.or_insert_with(|| Contact(HashMap::new()))
.0
.insert(field, value);
}
fn remove_field(&mut self, node: NodeId, field: String) {
if let Some(contact) = self.contacts.0.get_mut(&node) {
contact.0.remove(&field);
}
}
}
wit_bindgen::generate!({
path: "target/wit",
world: "process-v0",
world: "contacts-sys-v0",
generate_unused_types: true,
additional_derives: [PartialEq, serde::Deserialize, serde::Serialize],
});
call_init!(initialize);
fn initialize(our: Address) {
// add ourselves to the homepage
homepage::add_to_homepage("Contacts", Some(ICON), Some("/"), None);
// Grab our state, then enter the main event loop.
let mut state: ContactsState = ContactsState::new(our);
let mut http_server = http::server::HttpServer::new(5);
// Serve the index.html and other UI files found in pkg/ui at the root path.
// Serving securely at `settings-sys` subdomain
// serve the frontend on a secure subdomain
http_server
.serve_ui(
&state.our,
@ -52,8 +97,8 @@ fn initialize(our: Address) {
fn main_loop(state: &mut ContactsState, http_server: &mut http::server::HttpServer) {
loop {
match await_message() {
Err(send_error) => {
println!("got send error: {send_error:?}");
Err(_send_error) => {
// ignore send errors, local-only process
continue;
}
Ok(Message::Request {
@ -84,44 +129,25 @@ fn handle_request(
body: &[u8],
state: &mut ContactsState,
http_server: &mut http::server::HttpServer,
) -> SettingsResponse {
) -> Option<ContactsResponse> {
// source node is ALWAYS ourselves since networking is disabled
if source.process == "http_server:distro:sys" {
// receive HTTP requests and websocket connection messages from our server
let server_request = http_server
.parse_request(body)
.map_err(|_| SettingsError::MalformedRequest)?;
let server_request = http_server.parse_request(body).unwrap();
http_server.handle_request(
server_request,
|req| {
let result = handle_http_request(state, &req);
match result {
Ok((resp, blob)) => (resp, blob),
Err(e) => {
println!("error handling HTTP request: {e}");
(
http::server::HttpResponse {
status: 500,
headers: HashMap::new(),
},
Some(LazyLoadBlob {
mime: Some("application/text".to_string()),
bytes: e.to_string().as_bytes().to_vec(),
}),
)
}
}
},
|req| handle_http_request(state, &req),
|_channel_id, _message_type, _blob| {
// we don't expect websocket messages
},
);
Ok(None)
None
} else {
let settings_request = serde_json::from_slice::<SettingsRequest>(body)
.map_err(|_| SettingsError::MalformedRequest)?;
handle_settings_request(state, settings_request)
// let settings_request = serde_json::from_slice::<SettingsRequest>(body)
// .map_err(|_| SettingsError::MalformedRequest)?;
// handle_settings_request(state, settings_request)
None
}
}
@ -129,163 +155,62 @@ fn handle_request(
fn handle_http_request(
state: &mut ContactsState,
http_request: &http::server::IncomingHttpRequest,
) -> anyhow::Result<(http::server::HttpResponse, Option<LazyLoadBlob>)> {
match http_request.method()?.as_str() {
) -> (http::server::HttpResponse, Option<LazyLoadBlob>) {
match http_request.method().unwrap().as_str() {
"GET" => {
state.fetch()?;
Ok((
// state.fetch().unwrap();
(
http::server::HttpResponse::new(http::StatusCode::OK)
.header("Content-Type", "application/json"),
Some(LazyLoadBlob::new(
Some("application/json"),
serde_json::to_vec(&state)?,
serde_json::to_vec(&state).unwrap(),
)),
))
)
}
"POST" => {
let Some(blob) = get_blob() else {
return Err(anyhow::anyhow!("malformed request"));
};
let request = serde_json::from_slice::<SettingsRequest>(&blob.bytes)?;
let response = handle_settings_request(state, request);
Ok((
let blob = get_blob().unwrap();
let request = serde_json::from_slice::<ContactsRequest>(&blob.bytes).unwrap();
let response = handle_contacts_request(state, request);
let response: Option<String> = Some("ok".to_string());
(
http::server::HttpResponse::new(http::StatusCode::OK)
.header("Content-Type", "application/json"),
match response {
Ok(Some(data)) => Some(LazyLoadBlob::new(
Some(data) => Some(LazyLoadBlob::new(
Some("application/json"),
serde_json::to_vec(&data)?,
)),
Ok(None) => None,
Err(e) => Some(LazyLoadBlob::new(
Some("application/json"),
serde_json::to_vec(&e)?,
serde_json::to_vec(&data).unwrap(),
)),
None => None,
},
))
)
}
// Any other method will be rejected.
_ => Ok((
_ => (
http::server::HttpResponse::new(http::StatusCode::METHOD_NOT_ALLOWED),
None,
)),
),
}
}
fn handle_settings_request(
state: &mut SettingsState,
request: SettingsRequest,
) -> SettingsResponse {
fn handle_contacts_request(
state: &mut ContactsState,
request: ContactsRequest,
) -> ContactsResponse {
match request {
SettingsRequest::Hi {
node,
content,
timeout,
} => {
if let Err(SendError { kind, .. }) = Request::to((&node, "net", "distro", "sys"))
.body(content.into_bytes())
.send_and_await_response(timeout)
.unwrap()
{
match kind {
SendErrorKind::Timeout => {
println!("message to {node} timed out");
return Err(SettingsError::HiTimeout);
}
SendErrorKind::Offline => {
println!("{node} is offline or does not exist");
return Err(SettingsError::HiOffline);
}
}
} else {
return Ok(None);
}
}
SettingsRequest::PeerId(node) => {
// get peer info
match Request::to(("our", "net", "distro", "sys"))
.body(rmp_serde::to_vec(&net::NetAction::GetPeer(node)).unwrap())
.send_and_await_response(30)
.unwrap()
{
Ok(msg) => match rmp_serde::from_slice::<net::NetResponse>(msg.body()) {
Ok(net::NetResponse::Peer(Some(peer))) => {
println!("got peer info: {peer:?}");
return Ok(Some(SettingsData::PeerId(peer)));
}
Ok(net::NetResponse::Peer(None)) => {
println!("peer not found");
return Ok(None);
}
_ => {
return Err(SettingsError::KernelNonresponsive);
}
},
Err(_) => {
return Err(SettingsError::KernelNonresponsive);
}
}
}
SettingsRequest::EthConfig(action) => {
match Request::to(("our", "eth", "distro", "sys"))
.body(serde_json::to_vec(&action).unwrap())
.send_and_await_response(30)
.unwrap()
{
Ok(msg) => match serde_json::from_slice::<eth::EthConfigResponse>(msg.body()) {
Ok(eth::EthConfigResponse::PermissionDenied) => {
return Err(SettingsError::KernelNonresponsive);
}
Ok(other) => {
println!("eth config action succeeded: {other:?}");
}
Err(_) => {
return Err(SettingsError::KernelNonresponsive);
}
},
Err(_) => {
return Err(SettingsError::KernelNonresponsive);
}
}
}
SettingsRequest::Shutdown => {
// shutdown the node IMMEDIATELY!
Request::to(("our", "kernel", "distro", "sys"))
.body(serde_json::to_vec(&kernel_types::KernelCommand::Shutdown).unwrap())
.send()
.unwrap();
}
SettingsRequest::KillProcess(pid) => {
// kill a process
if let Err(_) = Request::to(("our", "kernel", "distro", "sys"))
.body(serde_json::to_vec(&kernel_types::KernelCommand::KillProcess(pid)).unwrap())
.send_and_await_response(30)
.unwrap()
{
return SettingsResponse::Err(SettingsError::KernelNonresponsive);
}
}
SettingsRequest::SetStylesheet(stylesheet) => {
let Ok(()) = kinode_process_lib::vfs::File {
path: "/homepage:sys/pkg/kinode.css".to_string(),
timeout: 5,
}
.write(stylesheet.as_bytes()) else {
return SettingsResponse::Err(SettingsError::KernelNonresponsive);
};
Request::to(("our", "homepage", "homepage", "sys"))
.body(
serde_json::json!({ "SetStylesheet": stylesheet })
.to_string()
.as_bytes(),
)
.send()
.unwrap();
state.stylesheet = Some(stylesheet);
return SettingsResponse::Ok(None);
}
ContactsRequest::GetNames => ContactsResponse::GetNames(
state
.contacts()
.0
.keys()
.map(|node| node.to_string())
.collect(),
),
ContactsRequest::GetAllContacts => ContactsResponse::GetAllContacts,
ContactsRequest::GetContact(node) => ContactsResponse::GetContact,
ContactsRequest::AddContact(node) => ContactsResponse::AddContact,
ContactsRequest::AddField((node, field, value)) => ContactsResponse::AddField,
ContactsRequest::RemoveContact(node) => ContactsResponse::RemoveContact,
ContactsRequest::RemoveField((node, field)) => ContactsResponse::RemoveField,
}
state.fetch().map_err(|_| SettingsError::StateFetchFailed)?;
SettingsResponse::Ok(None)
}

View File

@ -1,6 +1,6 @@
{
"name": "Contacts",
"description": "Store and manage your contacts.",
"description": "Save and manage your Kinode OS contacts.",
"image": "",
"properties": {
"package_name": "contacts",

View File

@ -6,13 +6,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="/kinode.css">
<link
href="https://api.fontshare.com/v2/css?f[]=clash-display@400,700,500,600,300&display=swap"
rel="stylesheet"
/>
<link href="https://api.fontshare.com/v2/css?f[]=clash-display@400,700,500,600,300&display=swap" rel="stylesheet" />
<script src="/our.js"></script>
<script>
document.title = window.our.node + " - settings";
document.title = window.our.node + " - contacts";
</script>
<style>
h1,
@ -40,195 +37,17 @@
grid-template-columns: 1fr 1fr 1fr;
gap: 20px 20px;
grid-auto-flow: row;
grid-template-areas:
"diagnostics diagnostics diagnostics"
"node-info pings pings"
"eth-rpc-providers eth-rpc-providers eth-rpc-settings"
"kernel kernel kernel"
"kinode-css kinode-css kinode-css";
padding: 20px;
max-width: 960px;
min-width: 300px;
}
article#net-diagnostics {
grid-area: diagnostics;
}
p#diagnostics,
p#peer-pki-response,
p#peer-ping-response {
white-space: pre-wrap;
}
article#node-info {
grid-area: node-info;
word-wrap: break-word;
display: flex;
flex-direction: column;
justify-content: space-around;
}
#shutdown {
background-color: var(--ansi-red)
}
#shutdown:hover {
background-color: var(--maroon);
}
article#pings {
grid-area: pings;
}
article#eth-rpc-providers {
grid-area: eth-rpc-providers;
}
article#eth-rpc-settings {
grid-area: eth-rpc-settings;
}
article#kernel {
grid-area: kernel;
}
article#kinode-css {
grid-area: kinode-css;
}
textarea#stylesheet-editor {
width: 100%;
min-width: 300px;
min-height: 400px;
}
div#provider-edits {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px 20px;
grid-auto-flow: row;
}
article {
border: 1px solid #444;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
max-height: 600px;
overflow-y: auto;
}
ul {
list-style-type: none;
padding: 0;
}
li {
padding: 8px;
margin-bottom: 6px;
border-radius: 4px;
word-wrap: break-word;
white-space: pre-wrap;
}
#process-map li p:first-child {
font-weight: bold;
}
#process-map li ul {
padding-left: 20px;
}
#process-map li ul li {
margin-bottom: 1px;
padding: 0;
}
button.kill-process {
padding: 3px 6px;
margin: 10px;
}
</style>
</head>
<body>
<h1>system diagnostics & settings</h1>
<h1>contacts</h1>
<main>
<article id="net-diagnostics">
<h2>networking diagnostics</h2>
<p id="diagnostics"></p>
</article>
<article id="node-info">
<h2>node info</h2>
<p id="node-name"></p>
<p id="net-key"></p>
<p id="ip-ports"></p>
<p id="routers"></p>
<button id="shutdown">shut down node(!)</button>
</article>
<article id="pings">
<h2>fetch PKI data</h2>
<form id="get-peer-pki">
<input type="text" name="peer" placeholder="peer-name.os">
<button type="submit">get peer info</button>
</form>
<p id="peer-pki-response"></p>
<h2>ping a node</h2>
<form id="ping-peer">
<input type="text" name="peer" placeholder="peer-name.os">
<input type="text" name="content" placeholder="message">
<input type="number" name="timeout" placeholder="timeout (seconds)">
<button type="submit">ping</button>
</form>
<p id="peer-ping-response"></p>
</article>
<article id="eth-rpc-providers">
<h2>ETH RPC providers</h2>
<div id="provider-edits">
<form id="add-eth-provider">
<input type="number" name="chain-id" placeholder="1">
<input type="text" name="rpc-url" placeholder="wss://rpc-url.com">
<button type="submit">add provider</button>
</form>
<form id="remove-eth-provider">
<input type="number" name="chain-id" placeholder="1">
<input type="text" name="rpc-url" placeholder="wss://rpc-url.com">
<button type="submit">remove provider</button>
</form>
</div>
<ul id="providers"></ul>
</article>
<article id="eth-rpc-settings">
<h2>ETH RPC settings</h2>
<p id="public"></p>
<div>
<p>nodes allowed to connect:</p>
<ul id="allowed-nodes"></ul>
</div>
<div>
<p>nodes banned from connecting:</p>
<ul id="denied-nodes"></ul>
</div>
</article>
<article id="kernel">
<h2>running processes</h2>
<ul id="process-map"></ul>
</article>
<article id="kinode-css">
<h2>stylesheet editor</h2>
<textarea id="stylesheet-editor"></textarea>
<button id="save-stylesheet">update kinode.css</button>
</article>
<script src="/settings:settings:sys/script.js"></script>
<script src="/contacts:contacts:sys/script.js"></script>
</main>
</body>

View File

@ -1,16 +1,16 @@
const APP_PATH = '/settings:settings:sys/ask';
const APP_PATH = '/contacts:contacts:sys/';
// Fetch initial data and populate the UI
function init() {
fetch(APP_PATH)
fetch(APP_PATH + 'get')
.then(response => response.json())
.then(data => {
populate(data);
});
}
function api_call(body) {
fetch(APP_PATH, {
function api_call(path, body) {
fetch(APP_PATH + path, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -19,302 +19,19 @@ function api_call(body) {
});
}
function shutdown() {
api_call("Shutdown");
setTimeout(() => {
window.location.reload();
}, 1000);
}
function populate(data) {
populate_node_info(data.identity);
populate_net_diagnostics(data.diagnostics);
populate_eth_rpc_providers(data.eth_rpc_providers);
populate_eth_rpc_settings(data.eth_rpc_access_settings);
populate_process_map(data.process_map);
populate_stylesheet_editor(data.stylesheet);
}
function populate_node_info(identity) {
document.getElementById('node-name').innerText = identity.name;
document.getElementById('net-key').innerText = identity.networking_key;
if (identity.ws_routing) {
document.getElementById('ip-ports').innerText = identity.ws_routing;
} else {
document.getElementById('ip-ports').style.display = 'none';
}
if (identity.routers) {
document.getElementById('routers').innerText = identity.routers;
} else {
document.getElementById('routers').style.display = 'none';
}
}
function populate_net_diagnostics(diagnostics) {
document.getElementById('diagnostics').innerText = diagnostics;
}
function populate_eth_rpc_providers(providers) {
const ul = document.getElementById('providers');
ul.innerHTML = '';
providers.forEach(provider => {
const li = document.createElement('li');
li.innerHTML = `${JSON.stringify(provider, undefined, 2)}`;
ul.appendChild(li);
});
}
function populate_eth_rpc_settings(settings) {
if (settings.public) {
document.getElementById('public').innerText = 'status: public';
document.getElementById('allowed-nodes').style.display = 'none';
} else {
document.getElementById('public').innerText = 'status: private';
const ul = document.getElementById('allowed-nodes');
ul.innerHTML = '';
if (settings.allow.length === 0) {
const li = document.createElement('li');
li.innerHTML = `<li>(none)</li>`;
ul.appendChild(li);
} else {
settings.allow.forEach(allowed_node => {
const li = document.createElement('li');
li.innerHTML = `<li>${allowed_node}</li>`;
ul.appendChild(li);
});
}
}
const ul = document.getElementById('denied-nodes');
ul.innerHTML = '';
if (settings.deny.length === 0) {
const li = document.createElement('li');
li.innerHTML = `<li>(none)</li>`;
ul.appendChild(li);
} else {
settings.deny.forEach(denied_node => {
const li = document.createElement('li');
li.innerHTML = `<li>${denied_node}</li>`;
ul.appendChild(li);
});
}
}
function populate_process_map(process_map) {
// apps we don't want user to kill, also runtime modules that cannot be killed
const do_not_kill = [
'settings:setting:sys',
'main:app_store:sys',
'net:distro:sys',
'kernel:distro:sys',
'kv:distro:sys',
'sqlite:distro:sys',
'eth:distro:sys',
'vfs:distro:sys',
'state:distro:sys',
'kns_indexer:kns_indexer:sys',
'http_client:distro:sys',
'http_server:distro:sys',
'terminal:terminal:sys',
'timer:distro:sys',
];
const ul = document.getElementById('process-map');
ul.innerHTML = '';
Object.entries(process_map).forEach(([id, process]) => {
const li = document.createElement('li');
const toggleButton = document.createElement('button');
toggleButton.textContent = `${id}`;
toggleButton.onclick = function () {
const details = this.nextElementSibling;
details.style.display = details.style.display === 'none' ? 'block' : 'none';
};
li.appendChild(toggleButton);
const detailsDiv = document.createElement('div');
detailsDiv.style.display = 'none';
if (!do_not_kill.includes(id)) {
const killButton = document.createElement('button');
killButton.className = 'kill-process';
killButton.setAttribute('data-id', id);
killButton.textContent = 'kill';
detailsDiv.appendChild(killButton);
}
const publicInfo = document.createElement('p');
publicInfo.textContent = `public: ${process.public}`;
detailsDiv.appendChild(publicInfo);
const onExit = document.createElement('p');
onExit.textContent = `on_exit: ${process.on_exit}`;
detailsDiv.appendChild(onExit);
if (process.wit_version) {
const witVersion = document.createElement('p');
witVersion.textContent = `wit_version: ${process.wit_version}`;
detailsDiv.appendChild(witVersion);
}
if (process.wasm_bytes_handle) {
const wasmBytesHandle = document.createElement('p');
wasmBytesHandle.textContent = `wasm_bytes_handle: ${process.wasm_bytes_handle}`;
detailsDiv.appendChild(wasmBytesHandle);
}
const capsList = document.createElement('ul');
process.capabilities.forEach(cap => {
const capLi = document.createElement('li');
capLi.textContent = `${cap.issuer}(${JSON.stringify(JSON.parse(cap.params), null, 2)})`;
capsList.appendChild(capLi);
});
detailsDiv.appendChild(capsList);
li.appendChild(detailsDiv);
ul.appendChild(li);
});
document.querySelectorAll('.kill-process').forEach(button => {
button.addEventListener('click', () => {
api_call({ "KillProcess": button.getAttribute('data-id') });
});
});
}
function populate_stylesheet_editor(stylesheet) {
document.getElementById('stylesheet-editor').value = stylesheet;
}
function save_stylesheet() {
const stylesheet = document.getElementById('stylesheet-editor').value;
api_call({ "SetStylesheet": stylesheet });
console.log(data);
}
// Call init to start the application
init();
// Setup event listeners
document.getElementById('shutdown').addEventListener('click', shutdown);
document.getElementById('save-stylesheet').addEventListener('click', save_stylesheet);
document.getElementById('get-peer-pki').addEventListener('submit', (e) => {
e.preventDefault();
const data = new FormData(e.target);
const body = {
"PeerId": data.get('peer'),
};
fetch(APP_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}).then(response => response.json())
.then(data => {
if (data === null) {
document.getElementById('peer-pki-response').innerText = "no pki data for peer";
} else {
e.target.reset();
document.getElementById('peer-pki-response').innerText = JSON.stringify(data, undefined, 2);
}
});
})
document.getElementById('ping-peer').addEventListener('submit', (e) => {
e.preventDefault();
const data = new FormData(e.target);
const body = {
"Hi": {
node: data.get('peer'),
content: data.get('content'),
timeout: Number(data.get('timeout')),
}
};
fetch(APP_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}).then(response => response.json())
.then(data => {
if (data === null) {
e.target.reset();
document.getElementById('peer-ping-response').innerText = "ping successful!";
} else if (data === "HiTimeout") {
document.getElementById('peer-ping-response').innerText = "node timed out";
} else if (data === "HiOffline") {
document.getElementById('peer-ping-response').innerText = "node is offline";
}
});
})
document.getElementById('add-eth-provider').addEventListener('submit', (e) => {
e.preventDefault();
const data = new FormData(e.target);
const rpc_url = data.get('rpc-url');
// validate rpc url
if (!rpc_url.startsWith('wss://') && !rpc_url.startsWith('ws://')) {
alert('Invalid RPC URL');
return;
}
const body = {
"EthConfig": {
"AddProvider": {
chain_id: Number(data.get('chain-id')),
trusted: false,
provider: { "RpcUrl": rpc_url },
}
}
};
fetch(APP_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}).then(response => response.json())
.then(data => {
if (data === null) {
e.target.reset();
return;
} else {
alert(data);
}
});
})
document.getElementById('remove-eth-provider').addEventListener('submit', (e) => {
e.preventDefault();
const data = new FormData(e.target);
const body = {
"EthConfig": {
"RemoveProvider": [Number(data.get('chain-id')), data.get('rpc-url')]
}
};
fetch(APP_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}).then(response => response.json())
.then(data => {
if (data === null) {
e.target.reset();
return;
} else {
alert(data);
}
});
})
// Setup WebSocket connection
const wsProtocol = location.protocol === 'https:' ? 'wss://' : 'ws://';
const ws = new WebSocket(wsProtocol + location.host + "/settings:settings:sys/");
ws.onmessage = event => {
const data = JSON.parse(event.data);
console.log(data);
populate(data);
};
// const wsProtocol = location.protocol === 'https:' ? 'wss://' : 'ws://';
// const ws = new WebSocket(wsProtocol + location.host + "/settings:settings:sys/");
// ws.onmessage = event => {
// const data = JSON.parse(event.data);
// console.log(data);
// populate(data);
// };