162 lines
5.6 KiB
Rust
162 lines
5.6 KiB
Rust
use crate::btrfs_structs::{Item, Key, ItemType, Value, ExtentDataBody};
|
|
|
|
#[derive(Debug)]
|
|
pub struct TableResult<'a> {
|
|
pub tree_id: u64,
|
|
pub tree_desc: Option<String>,
|
|
pub key_id: Option<u64>,
|
|
pub items: Vec<(&'a Item, &'a [u8])>,
|
|
pub first_key: Key,
|
|
pub last_key: Key,
|
|
}
|
|
|
|
static HTML_HEADER: &str = r###"<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<link rel="stylesheet" href="/style.css">
|
|
</head>
|
|
<body>
|
|
"###;
|
|
|
|
static HTML_FOOTER: &str = r###"
|
|
<script src="/htmx.min.js"></script>
|
|
</body>
|
|
</html>
|
|
"###;
|
|
|
|
pub fn render_table(table: TableResult) -> String {
|
|
let mut result = String::new();
|
|
|
|
result.push_str(HTML_HEADER);
|
|
|
|
// header
|
|
if let Some(desc) = table.tree_desc {
|
|
result.push_str(&format!("<h1>Tree {} ({})</h1>", table.tree_id, desc));
|
|
} else {
|
|
result.push_str(&format!("<h1>Tree {}</h1>", table.tree_id));
|
|
}
|
|
|
|
// link to root tree
|
|
if table.tree_id != 1 {
|
|
result.push_str(&format!("<a href=\"/tree/1\">go back to root tree</a>"));
|
|
}
|
|
|
|
// search field
|
|
let key_input_value = table.key_id.map_or(String::new(), |x| format!("{:X}", x));
|
|
result.push_str(&format!("<form method=get action=\"/tree/{}\">\n", table.tree_id));
|
|
result.push_str(&format!("<input type=text name=\"key\" value=\"{}\">\n", key_input_value));
|
|
result.push_str("<input type=submit value=\"Search\">\n");
|
|
result.push_str("</form>\n");
|
|
|
|
// navigation links
|
|
// technically, adding one to offset in "next" is not correct if offset is -1
|
|
// it would show an entry twice in that case, but who cares, that never actually happens anyway
|
|
let first = table.first_key;
|
|
let last = table.last_key;
|
|
result.push_str(&format!("<a href=\"/tree/{}\">first</a>\n",
|
|
table.tree_id));
|
|
result.push_str(&format!("<a href=\"/tree/{}/to/{:016X}-{:02X}-{:016X}\">prev</a>\n",
|
|
table.tree_id, first.key_id, u8::from(first.key_type), first.key_offset));
|
|
result.push_str(&format!("<a href=\"/tree/{}/from/{:016X}-{:02X}-{:016X}\">next</a>\n",
|
|
table.tree_id, last.key_id, u8::from(last.key_type), last.key_offset));
|
|
result.push_str(&format!("<a href=\"/tree/{}/to/{:016X}-{:02X}-{:016X}\">last</a>\n",
|
|
table.tree_id, u64::wrapping_sub(0,1), u8::wrapping_sub(0,1), u64::wrapping_sub(0,1)));
|
|
|
|
// the actual table
|
|
result.push_str("<table><tbody>\n");
|
|
for (idx, (it, _it_data)) in table.items.iter().enumerate() {
|
|
let highlighted = table.key_id.filter(|x|*x == it.key.key_id).is_some();
|
|
let value_string = item_value_string(table.tree_id, it.key, &it.value);
|
|
let raw_string = format!("{:#?}", it);
|
|
let id_desc = row_id_desc(it.key, table.tree_id);
|
|
|
|
let key_type_class = match it.key.key_type {
|
|
ItemType::Inode => "inode",
|
|
ItemType::Ref => "ref",
|
|
ItemType::RootRef => "ref",
|
|
ItemType::RootBackRef => "ref",
|
|
ItemType::ExtentData => "extent",
|
|
ItemType::Dir => "dir",
|
|
ItemType::DirIndex => "dir",
|
|
ItemType::Root => "root",
|
|
_ => "",
|
|
};
|
|
let row = format!("<details{}><summary><span class=\"key key_id {key_type_class}\">{}</span><span class=\"key key_type {key_type_class}\">{}</span><span class=\"key key_offset {key_type_class}\">{}</span><span class=\"itemvalue\">{}</span></summary><div class=\"details\">{}</div></details>\n",
|
|
if highlighted { " class = \"highlight\"" } else { "" },
|
|
id_desc.0, id_desc.1, id_desc.2, &value_string, &raw_string);
|
|
|
|
|
|
result.push_str(&row);
|
|
}
|
|
result.push_str("</tbody></table>\n");
|
|
|
|
result.push_str(HTML_FOOTER);
|
|
|
|
result
|
|
}
|
|
|
|
fn row_id_desc(key: Key, tree_id: u64) -> (String, String, String) {
|
|
let x = format!("{:X}", key.key_id);
|
|
let y = format!("{:?} ({:02X})", key.key_type, u8::from(key.key_type));
|
|
let z = if key.key_type == ItemType::RootRef || key.key_type == ItemType::Ref {
|
|
format!("<a href=\"/tree/{}/{:X}\">{:X}</a>", tree_id, key.key_offset, key.key_offset)
|
|
} else {
|
|
format!("{:X}", key.key_offset)
|
|
};
|
|
(x,y,z)
|
|
}
|
|
|
|
fn item_value_string(tree_id: u64, key: Key, val: &Value) -> String {
|
|
match val {
|
|
Value::Root(_) => {
|
|
format!("<a href=\"/tree/{}\">go to tree {}</a>", key.key_id, key.key_id)
|
|
},
|
|
Value::Dir(dir_item) =>
|
|
format!("{:?} @ <a href=\"/tree/{}/{:x}\">{:X}</a>",
|
|
&dir_item.name,
|
|
tree_id,
|
|
dir_item.location.key_id,
|
|
dir_item.location.key_id),
|
|
Value::DirIndex(dir_item) =>
|
|
format!("{:?} @ <a href=\"/tree/{}/{:x}\">{:X}</a>",
|
|
&dir_item.name,
|
|
tree_id,
|
|
dir_item.location.key_id,
|
|
dir_item.location.key_id),
|
|
Value::Inode(inode_item) =>
|
|
String::new(),
|
|
/* format!("mode: {:o}, ctime: {}, mtime: {}, otime: {}",
|
|
inode_item.mode,
|
|
inode_item.ctime.sec,
|
|
inode_item.mtime.sec,
|
|
inode_item.otime.sec), */
|
|
Value::ExtentData(extent_data_item) =>
|
|
match &extent_data_item.data {
|
|
ExtentDataBody::Inline(data) => format!("inline, length {}", data.len()),
|
|
ExtentDataBody::External(ext_extent) =>
|
|
format!("external, length {}",
|
|
ext_extent.num_bytes),
|
|
},
|
|
Value::Ref(ref_item) =>
|
|
format!("{:?}", &ref_item.name),
|
|
Value::Extent(extent_item) =>
|
|
format!("flags: {}, block_refs: {:?}", extent_item.flags, extent_item.block_refs),
|
|
Value::BlockGroup(blockgroup_item) =>
|
|
format!("{} bytes used", blockgroup_item.used),
|
|
Value::DevExtent(dev_extent_item) =>
|
|
format!("chunk_tree: {}, chunk_offset: {:x}, length: {} bytes", dev_extent_item.chunk_tree, dev_extent_item.chunk_offset, dev_extent_item.length),
|
|
Value::UUIDSubvol(uuid_subvol_item) =>
|
|
format!("subvolume id: {}", uuid_subvol_item.subvol_id),
|
|
Value::FreeSpaceInfo(free_space_info) =>
|
|
format!("extent_count: {}, flags: {}", free_space_info.extent_count, free_space_info.flags),
|
|
Value::Dev(dev_item) =>
|
|
format!("total_bytes: {}", dev_item.total_bytes),
|
|
Value::Chunk(chunk_item) =>
|
|
format!("size: {}", chunk_item.size),
|
|
_ => {
|
|
println!("{:?} {:?}", key, val);
|
|
String::new()
|
|
},
|
|
}
|
|
}
|