get homepage/register mixup sorted out; serve app data on homepage api route
@ -2,8 +2,8 @@
|
|||||||
use kinode_process_lib::{
|
use kinode_process_lib::{
|
||||||
await_message, call_init, get_blob,
|
await_message, call_init, get_blob,
|
||||||
http::{
|
http::{
|
||||||
bind_http_path, bind_http_static_path, get_mime_type, serve_index_html, serve_ui,
|
bind_http_path, bind_http_static_path, get_mime_type, send_response, serve_index_html,
|
||||||
HttpServerError,
|
serve_ui, HttpServerError, HttpServerRequest, IncomingHttpRequest, StatusCode,
|
||||||
},
|
},
|
||||||
println,
|
println,
|
||||||
vfs::{FileType, VfsAction, VfsRequest, VfsResponse},
|
vfs::{FileType, VfsAction, VfsRequest, VfsResponse},
|
||||||
@ -27,6 +27,14 @@ enum HomepageRequest {
|
|||||||
Remove,
|
Remove,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct HomepageApp {
|
||||||
|
package_name: String,
|
||||||
|
path: String,
|
||||||
|
label: String,
|
||||||
|
base64_icon: String,
|
||||||
|
}
|
||||||
|
|
||||||
wit_bindgen::generate!({
|
wit_bindgen::generate!({
|
||||||
path: "wit",
|
path: "wit",
|
||||||
world: "process",
|
world: "process",
|
||||||
@ -35,7 +43,7 @@ wit_bindgen::generate!({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const HOME_PAGE: &str = include_str!("../../pkg/ui/index.html");
|
const HOME_PAGE: &str = include_str!("index.html");
|
||||||
|
|
||||||
const APP_TEMPLATE: &str = r#"
|
const APP_TEMPLATE: &str = r#"
|
||||||
<a class="app-link" id="${package_name}" href="/${path}">
|
<a class="app-link" id="${package_name}" href="/${path}">
|
||||||
@ -46,95 +54,121 @@ const APP_TEMPLATE: &str = r#"
|
|||||||
|
|
||||||
call_init!(init);
|
call_init!(init);
|
||||||
|
|
||||||
// Copied in from process_lib serve_ui. see https://github.com/kinode-dao/process_lib/blob/main/src/http.rs
|
/// bind to root path on http_server (we have special dispensation to do so!)
|
||||||
fn static_serve_dir(
|
fn bind_index(our: &str, apps: &HashMap<ProcessId, String>) {
|
||||||
our: &Address,
|
|
||||||
directory: &str,
|
|
||||||
authenticated: bool,
|
|
||||||
local_only: bool,
|
|
||||||
paths: Vec<&str>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
serve_index_html(our, directory, authenticated, local_only, paths)?;
|
|
||||||
|
|
||||||
let initial_path = format!("{}/pkg/{}", our.package_id(), directory);
|
|
||||||
println!("initial path: {}", initial_path);
|
|
||||||
|
|
||||||
let mut queue = VecDeque::new();
|
|
||||||
queue.push_back(initial_path.clone());
|
|
||||||
|
|
||||||
while let Some(path) = queue.pop_front() {
|
|
||||||
let Ok(directory_response) = KiRequest::to(("our", "vfs", "distro", "sys"))
|
|
||||||
.body(serde_json::to_vec(&VfsRequest {
|
|
||||||
path,
|
|
||||||
action: VfsAction::ReadDir,
|
|
||||||
})?)
|
|
||||||
.send_and_await_response(5)?
|
|
||||||
else {
|
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"serve_ui: no response for path: {}",
|
|
||||||
initial_path
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let directory_body = serde_json::from_slice::<VfsResponse>(directory_response.body())?;
|
|
||||||
|
|
||||||
// Determine if it's a file or a directory and handle appropriately
|
|
||||||
match directory_body {
|
|
||||||
VfsResponse::ReadDir(directory_info) => {
|
|
||||||
for entry in directory_info {
|
|
||||||
match entry.file_type {
|
|
||||||
// If it's a file, serve it statically
|
|
||||||
FileType::File => {
|
|
||||||
KiRequest::to(("our", "vfs", "distro", "sys"))
|
|
||||||
.body(serde_json::to_vec(&VfsRequest {
|
|
||||||
path: entry.path.clone(),
|
|
||||||
action: VfsAction::Read,
|
|
||||||
})?)
|
|
||||||
.send_and_await_response(5)??;
|
|
||||||
|
|
||||||
let Some(blob) = get_blob() else {
|
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"serve_ui: no blob for {}",
|
|
||||||
entry.path
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let content_type = get_mime_type(&entry.path);
|
|
||||||
|
|
||||||
println!("binding {}", entry.path.replace(&initial_path, ""));
|
|
||||||
|
|
||||||
bind_http_static_path(
|
bind_http_static_path(
|
||||||
entry.path.replace(&initial_path, ""),
|
"/",
|
||||||
authenticated, // Must be authenticated
|
true,
|
||||||
local_only, // Is not local-only
|
false,
|
||||||
Some(content_type),
|
Some("text/html".to_string()),
|
||||||
blob.bytes,
|
HOME_PAGE
|
||||||
)?;
|
.replace("${our}", our)
|
||||||
}
|
.replace(
|
||||||
FileType::Directory => {
|
"${apps}",
|
||||||
// Push the directory onto the queue
|
&apps
|
||||||
queue.push_back(entry.path);
|
.values()
|
||||||
}
|
.map(String::as_str)
|
||||||
_ => {}
|
.collect::<Vec<&str>>()
|
||||||
}
|
.join("\n"),
|
||||||
}
|
)
|
||||||
}
|
.to_string()
|
||||||
_ => {
|
.as_bytes()
|
||||||
return Err(anyhow::anyhow!(
|
.to_vec(),
|
||||||
"serve_ui: unexpected response for path: {:?}",
|
)
|
||||||
directory_body
|
.expect("failed to bind to /");
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // Copied in from process_lib serve_ui. see https://github.com/kinode-dao/process_lib/blob/main/src/http.rs
|
||||||
|
// fn static_serve_dir(
|
||||||
|
// our: &Address,
|
||||||
|
// directory: &str,
|
||||||
|
// authenticated: bool,
|
||||||
|
// local_only: bool,
|
||||||
|
// paths: Vec<&str>,
|
||||||
|
// ) -> anyhow::Result<()> {
|
||||||
|
// serve_index_html(our, directory, authenticated, local_only, paths)?;
|
||||||
|
|
||||||
|
// let initial_path = format!("{}/pkg/{}", our.package_id(), directory);
|
||||||
|
// println!("initial path: {}", initial_path);
|
||||||
|
|
||||||
|
// let mut queue = VecDeque::new();
|
||||||
|
// queue.push_back(initial_path.clone());
|
||||||
|
|
||||||
|
// while let Some(path) = queue.pop_front() {
|
||||||
|
// let Ok(directory_response) = KiRequest::to(("our", "vfs", "distro", "sys"))
|
||||||
|
// .body(serde_json::to_vec(&VfsRequest {
|
||||||
|
// path,
|
||||||
|
// action: VfsAction::ReadDir,
|
||||||
|
// })?)
|
||||||
|
// .send_and_await_response(5)?
|
||||||
|
// else {
|
||||||
|
// return Err(anyhow::anyhow!(
|
||||||
|
// "serve_ui: no response for path: {}",
|
||||||
|
// initial_path
|
||||||
|
// ));
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let directory_body = serde_json::from_slice::<VfsResponse>(directory_response.body())?;
|
||||||
|
|
||||||
|
// // Determine if it's a file or a directory and handle appropriately
|
||||||
|
// match directory_body {
|
||||||
|
// VfsResponse::ReadDir(directory_info) => {
|
||||||
|
// for entry in directory_info {
|
||||||
|
// match entry.file_type {
|
||||||
|
// // If it's a file, serve it statically
|
||||||
|
// FileType::File => {
|
||||||
|
// KiRequest::to(("our", "vfs", "distro", "sys"))
|
||||||
|
// .body(serde_json::to_vec(&VfsRequest {
|
||||||
|
// path: entry.path.clone(),
|
||||||
|
// action: VfsAction::Read,
|
||||||
|
// })?)
|
||||||
|
// .send_and_await_response(5)??;
|
||||||
|
|
||||||
|
// let Some(blob) = get_blob() else {
|
||||||
|
// return Err(anyhow::anyhow!(
|
||||||
|
// "serve_ui: no blob for {}",
|
||||||
|
// entry.path
|
||||||
|
// ));
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let content_type = get_mime_type(&entry.path);
|
||||||
|
|
||||||
|
// println!("binding {}", entry.path.replace(&initial_path, ""));
|
||||||
|
|
||||||
|
// bind_http_static_path(
|
||||||
|
// entry.path.replace(&initial_path, ""),
|
||||||
|
// authenticated, // Must be authenticated
|
||||||
|
// local_only, // Is not local-only
|
||||||
|
// Some(content_type),
|
||||||
|
// blob.bytes,
|
||||||
|
// )?;
|
||||||
|
// }
|
||||||
|
// FileType::Directory => {
|
||||||
|
// // Push the directory onto the queue
|
||||||
|
// queue.push_back(entry.path);
|
||||||
|
// }
|
||||||
|
// _ => {}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// _ => {
|
||||||
|
// return Err(anyhow::anyhow!(
|
||||||
|
// "serve_ui: unexpected response for path: {:?}",
|
||||||
|
// directory_body
|
||||||
|
// ))
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
||||||
fn init(our: Address) {
|
fn init(our: Address) {
|
||||||
let mut apps: HashMap<ProcessId, String> = HashMap::new();
|
let mut apps: HashMap<ProcessId, String> = HashMap::new();
|
||||||
|
let mut app_data: HashMap<ProcessId, HomepageApp> = HashMap::new();
|
||||||
|
|
||||||
static_serve_dir(&our, "ui", true, false, vec!["/", "/login"]);
|
// static_serve_dir(&our, "index.html", true, false, vec!["/"]);
|
||||||
|
bind_index(&our.node, &apps);
|
||||||
|
|
||||||
bind_http_static_path(
|
bind_http_static_path(
|
||||||
"/our",
|
"/our",
|
||||||
@ -156,6 +190,8 @@ fn init(our: Address) {
|
|||||||
)
|
)
|
||||||
.expect("failed to bind to /our.js");
|
.expect("failed to bind to /our.js");
|
||||||
|
|
||||||
|
bind_http_path("/apps", true, true).expect("failed to bind /apps");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let Ok(ref message) = await_message() else {
|
let Ok(ref message) = await_message() else {
|
||||||
// we never send requests, so this will never happen
|
// we never send requests, so this will never happen
|
||||||
@ -175,6 +211,15 @@ fn init(our: Address) {
|
|||||||
if let Ok(request) = serde_json::from_slice::<HomepageRequest>(message.body()) {
|
if let Ok(request) = serde_json::from_slice::<HomepageRequest>(message.body()) {
|
||||||
match request {
|
match request {
|
||||||
HomepageRequest::Add { label, icon, path } => {
|
HomepageRequest::Add { label, icon, path } => {
|
||||||
|
app_data.insert(
|
||||||
|
message.source().process.clone(),
|
||||||
|
HomepageApp {
|
||||||
|
package_name: message.source().clone().package().to_string(),
|
||||||
|
path: path.clone(),
|
||||||
|
label: label.clone(),
|
||||||
|
base64_icon: icon.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
apps.insert(
|
apps.insert(
|
||||||
message.source().process.clone(),
|
message.source().process.clone(),
|
||||||
APP_TEMPLATE
|
APP_TEMPLATE
|
||||||
@ -197,13 +242,43 @@ fn init(our: Address) {
|
|||||||
.replace("${label}", &label)
|
.replace("${label}", &label)
|
||||||
.replace("${base64_icon}", &icon),
|
.replace("${base64_icon}", &icon),
|
||||||
);
|
);
|
||||||
// bind_index(&our.node, &apps);
|
bind_index(&our.node, &apps);
|
||||||
}
|
}
|
||||||
HomepageRequest::Remove => {
|
HomepageRequest::Remove => {
|
||||||
apps.remove(&message.source().process);
|
apps.remove(&message.source().process);
|
||||||
// bind_index(&our.node, &apps);
|
bind_index(&our.node, &apps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if let Ok(request) = serde_json::from_slice::<HttpServerRequest>(message.body())
|
||||||
|
{
|
||||||
|
match request {
|
||||||
|
HttpServerRequest::Http(incoming) => {
|
||||||
|
let path = incoming.bound_path(None);
|
||||||
|
println!("on path: {}", path);
|
||||||
|
if path == "/apps" {
|
||||||
|
send_response(
|
||||||
|
StatusCode::OK,
|
||||||
|
Some(HashMap::from([(
|
||||||
|
"Content-Type".to_string(),
|
||||||
|
"application/json".to_string(),
|
||||||
|
)])),
|
||||||
|
app_data
|
||||||
|
.values()
|
||||||
|
.map(|app| serde_json::to_string(app).unwrap())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n")
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
send_response(
|
||||||
|
StatusCode::OK,
|
||||||
|
Some(HashMap::new()),
|
||||||
|
"hello".as_bytes().to_vec(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,10 @@
|
|||||||
"on_exit": "Restart",
|
"on_exit": "Restart",
|
||||||
"request_networking": false,
|
"request_networking": false,
|
||||||
"request_capabilities": [
|
"request_capabilities": [
|
||||||
"http_server:distro:sys",
|
"http_server:distro:sys"
|
||||||
"vfs:distro:sys"
|
|
||||||
],
|
],
|
||||||
"grant_capabilities": [
|
"grant_capabilities": [
|
||||||
"http_server:distro:sys",
|
"http_server:distro:sys"
|
||||||
"vfs:distro:sys"
|
|
||||||
],
|
],
|
||||||
"public": false
|
"public": false
|
||||||
}
|
}
|
||||||
|
@ -38,8 +38,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "GENERATE_SOURCEMAP=false react-scripts build",
|
"build": "GENERATE_SOURCEMAP=false react-scripts build",
|
||||||
"build:copy": "npm run build && npm run copy",
|
"build:copy": "npm run build",
|
||||||
"copy": "mkdir -p ../../../src/register-ui/build && rm -rf ../../../src/register-ui/build/* && cp -r build/* ../../../src/register-ui/build/",
|
|
||||||
"inline": "node ./add-inline-tags.js && cd build && inline-source ./index.html > ./inline-index.html && cd ..",
|
"inline": "node ./add-inline-tags.js && cd build && inline-source ./index.html > ./inline-index.html && cd ..",
|
||||||
"build-inline": "npm run build && npm run inline",
|
"build-inline": "npm run build && npm run inline",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 431 B After Width: | Height: | Size: 431 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |