display items as table rows
This commit is contained in:
parent
e2fb0cbb47
commit
80942c8ed3
@ -2,8 +2,8 @@ use std::convert::identity;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::ops::{Deref, RangeBounds, Bound};
|
use std::ops::{Deref, RangeBounds, Bound};
|
||||||
|
|
||||||
use crate::btrfs_structs::{Leaf, Key, Item, InteriorNode, Node, ParseError, ParseBin, Value, Superblock, ItemType, ZERO_KEY, DirItem};
|
use crate::btrfs_structs::{Leaf, Key, Item, InteriorNode, Node, ParseError, ParseBin, Value, Superblock, ItemType, ZERO_KEY};
|
||||||
use crate::addrmap::{node_at_log, LogToPhys, AddressMap};
|
use crate::addrmap::{node_at_log, AddressMap};
|
||||||
|
|
||||||
/// Represents a B-Tree inside a filesystem image. Can be used to look up keys,
|
/// Represents a B-Tree inside a filesystem image. Can be used to look up keys,
|
||||||
/// and handles the tree traversal and the virtual address translation.
|
/// and handles the tree traversal and the virtual address translation.
|
||||||
@ -40,9 +40,16 @@ impl<'a> Tree<'a> {
|
|||||||
Ok(Tree { image, addr_map, root_addr_log: superblock.root })
|
Ok(Tree { image, addr_map, root_addr_log: superblock.root })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn chunk(image: &'a [u8]) -> Result<Tree<'a>, ParseError> {
|
||||||
|
let addr_map = Rc::new(AddressMap::new(image)?);
|
||||||
|
let superblock = Superblock::parse(&image[0x10000..])?;
|
||||||
|
|
||||||
|
Ok(Tree { image, addr_map, root_addr_log: superblock.chunk_root })
|
||||||
|
}
|
||||||
|
|
||||||
/// Read a node given its logical address
|
/// Read a node given its logical address
|
||||||
pub fn get_node(&self, addr: u64) -> Result<Rc<Node>, ParseError> {
|
pub fn get_node(&self, addr: u64) -> Result<Rc<Node>, ParseError> {
|
||||||
eprintln!("Reading node at {:x}", addr);
|
// eprintln!("Reading node at {:x}", addr);
|
||||||
|
|
||||||
let node_data = node_at_log(self.image, self.addr_map.as_ref(), addr)?;
|
let node_data = node_at_log(self.image, self.addr_map.as_ref(), addr)?;
|
||||||
let node = Node::parse(node_data)?;
|
let node = Node::parse(node_data)?;
|
||||||
|
@ -311,63 +311,63 @@ pub struct RootItem {
|
|||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Debug,Clone,ParseBin)]
|
#[derive(Debug,Clone,ParseBin)]
|
||||||
pub struct DirItem {
|
pub struct DirItem {
|
||||||
location: Key,
|
pub location: Key,
|
||||||
transid: u64,
|
pub transid: u64,
|
||||||
data_len: u16,
|
pub data_len: u16,
|
||||||
name_len: u16,
|
pub name_len: u16,
|
||||||
dir_type: u8,
|
pub dir_type: u8,
|
||||||
|
|
||||||
// #[len = "name_len"]
|
// #[len = "name_len"]
|
||||||
name: CString,
|
pub name: CString,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Debug,Clone,ParseBin)]
|
#[derive(Debug,Clone,ParseBin)]
|
||||||
pub struct FreeSpaceInfoItem {
|
pub struct FreeSpaceInfoItem {
|
||||||
extent_count: u32,
|
pub extent_count: u32,
|
||||||
flags: u32,
|
pub flags: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Debug,Clone,ParseBin)]
|
#[derive(Debug,Clone,ParseBin)]
|
||||||
pub struct UUIDSubvolItem {
|
pub struct UUIDSubvolItem {
|
||||||
subvol_id: u64,
|
pub subvol_id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Debug,Clone,ParseBin)]
|
#[derive(Debug,Clone,ParseBin)]
|
||||||
pub struct DevItem {
|
pub struct DevItem {
|
||||||
devid: u64,
|
pub devid: u64,
|
||||||
total_bytes: u64,
|
pub total_bytes: u64,
|
||||||
bytes_used: u64,
|
pub bytes_used: u64,
|
||||||
io_align: u32,
|
pub io_align: u32,
|
||||||
io_width: u32,
|
pub io_width: u32,
|
||||||
sector_size: u32,
|
pub sector_size: u32,
|
||||||
dev_type: u64,
|
pub dev_type: u64,
|
||||||
generation: u64,
|
pub generation: u64,
|
||||||
start_offset: u64,
|
pub start_offset: u64,
|
||||||
dev_group: u32,
|
pub dev_group: u32,
|
||||||
seek_speed: u8,
|
pub seek_speed: u8,
|
||||||
bandwidth: u8,
|
pub bandwidth: u8,
|
||||||
uuid: UUID,
|
pub uuid: UUID,
|
||||||
fsid: UUID,
|
pub fsid: UUID,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Debug,Clone,ParseBin)]
|
#[derive(Debug,Clone,ParseBin)]
|
||||||
pub struct DevExtentItem {
|
pub struct DevExtentItem {
|
||||||
chunk_tree: u64,
|
pub chunk_tree: u64,
|
||||||
chunk_objectid: u64,
|
pub chunk_objectid: u64,
|
||||||
chunk_offset: u64,
|
pub chunk_offset: u64,
|
||||||
length: u64,
|
pub length: u64,
|
||||||
chunk_tree_uuid: UUID,
|
pub chunk_tree_uuid: UUID,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug,Clone)]
|
||||||
pub struct ExtentDataItem {
|
pub struct ExtentDataItem {
|
||||||
header: ExtentDataHeader,
|
pub header: ExtentDataHeader,
|
||||||
data: ExtentDataBody,
|
pub data: ExtentDataBody,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
@ -380,31 +380,31 @@ pub enum ExtentDataBody {
|
|||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Debug,Clone,ParseBin)]
|
#[derive(Debug,Clone,ParseBin)]
|
||||||
pub struct ExternalExtent {
|
pub struct ExternalExtent {
|
||||||
disk_bytenr: u64,
|
pub disk_bytenr: u64,
|
||||||
disk_num_bytes: u64,
|
pub disk_num_bytes: u64,
|
||||||
offset: u64,
|
pub offset: u64,
|
||||||
num_bytes: u64,
|
pub num_bytes: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Debug,Clone,ParseBin)]
|
#[derive(Debug,Clone,ParseBin)]
|
||||||
pub struct ExtentDataHeader {
|
pub struct ExtentDataHeader {
|
||||||
generation: u64,
|
pub generation: u64,
|
||||||
ram_bytes: u64,
|
pub ram_bytes: u64,
|
||||||
compression: u8,
|
pub compression: u8,
|
||||||
encryption: u8,
|
pub encryption: u8,
|
||||||
other_encoding: u16,
|
pub other_encoding: u16,
|
||||||
extent_type: u8,
|
pub extent_type: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Debug,Clone,ParseBin)]
|
#[derive(Debug,Clone,ParseBin)]
|
||||||
pub struct RefItem {
|
pub struct RefItem {
|
||||||
index: u64,
|
pub index: u64,
|
||||||
name_len: u16,
|
pub name_len: u16,
|
||||||
|
|
||||||
// #[len = "name_len"]
|
// #[len = "name_len"]
|
||||||
name: Vec<u8>,
|
pub name: CString,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
@ -721,6 +721,7 @@ impl fmt::Debug for Checksum {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
macro_rules! key {
|
macro_rules! key {
|
||||||
($arg1:expr) => {
|
($arg1:expr) => {
|
||||||
btrfs_structs::Key { key_id: $arg1, key_type: btrfs_structs::ItemType::Invalid, key_offset: 0 }
|
btrfs_structs::Key { key_id: $arg1, key_type: btrfs_structs::ItemType::Invalid, key_offset: 0 }
|
||||||
@ -733,4 +734,4 @@ macro_rules! key {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) use key;
|
//pub(crate) use key;
|
||||||
|
195
src/main.rs
195
src/main.rs
@ -3,12 +3,14 @@ use std::{
|
|||||||
env,
|
env,
|
||||||
fs::OpenOptions,
|
fs::OpenOptions,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
str::FromStr,
|
||||||
|
fs::File,
|
||||||
};
|
};
|
||||||
use memmap2::Mmap;
|
use memmap2::Mmap;
|
||||||
use rouille::{Request, Response, router};
|
use rouille::{Request, Response, router};
|
||||||
use parsebtrfs::{
|
use parsebtrfs::{
|
||||||
btrfs_structs::{TreeID, Value::Extent, Value::BlockGroup, ParseError, NODE_SIZE, ItemType},
|
btrfs_structs::{TreeID, Value::Extent, Value::BlockGroup, ParseError, NODE_SIZE, ItemType, Item, RootItem, Value, Key, ExtentDataBody},
|
||||||
btrfs_lookup::Tree,
|
btrfs_lookup::Tree, key,
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLORS: &[&str] = &["#e6194b", "#3cb44b", "#ffe119", "#4363d8", "#f58231", "#911eb4", "#46f0f0", "#f032e6", "#bcf60c", "#fabebe", "#008080", "#e6beff", "#9a6324", "#fffac8", "#800000", "#aaffc3", "#808000", "#ffd8b1", "#000075", "#808080", "#000000"];
|
const COLORS: &[&str] = &["#e6194b", "#3cb44b", "#ffe119", "#4363d8", "#f58231", "#911eb4", "#46f0f0", "#f032e6", "#bcf60c", "#fabebe", "#008080", "#e6beff", "#9a6324", "#fffac8", "#800000", "#aaffc3", "#808000", "#ffd8b1", "#000075", "#808080", "#000000"];
|
||||||
@ -38,7 +40,11 @@ fn main() -> Result<(), MainError> {
|
|||||||
router!(
|
router!(
|
||||||
request,
|
request,
|
||||||
(GET) ["/"] => http_main_boxes(&image, request),
|
(GET) ["/"] => http_main_boxes(&image, request),
|
||||||
|
(GET) ["/root"] => http_root(&image, None, request),
|
||||||
|
(GET) ["/tree/{tree}", tree: String] => http_tree(&image, &tree, None, request),
|
||||||
|
(GET) ["/tree/{tree}/{key}", tree: String, key: String] => http_tree(&image, &tree, Some(&key), request),
|
||||||
(GET) ["/favicon.ico"] => Response::empty_404(),
|
(GET) ["/favicon.ico"] => Response::empty_404(),
|
||||||
|
(GET) ["/style.css"] => Response::from_file("text/css", File::open("style.css").unwrap()),
|
||||||
_ => Response::empty_404(),
|
_ => Response::empty_404(),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@ -46,17 +52,31 @@ fn main() -> Result<(), MainError> {
|
|||||||
|
|
||||||
static CIRCLE_IMAGE: &str =
|
static CIRCLE_IMAGE: &str =
|
||||||
"data:image/png;base64,\
|
"data:image/png;base64,\
|
||||||
iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9\
|
iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAP0lEQVQY02NgoBn4//+//P///yf9\
|
||||||
kT1Iw0AcxV9bpUUqInYo4pChOtnFLxxrFYpQIdQKrTqYXPoFTRqSFBdHwbXg4Mdi1cHFWVcHV0EQ\
|
////DRRP+v//vzw2hZP+Y4JJ2BS+waLwDUyeiVinIStchkV+GfmeoRoAAJqLWnEf4UboAAAAAElF\
|
||||||
/ABxdXFSdJES/5cUWsR4cNyPd/ced+8Af7PKVLMnAaiaZWRSSSGXXxWCrwhhEAFEMS0xU58TxTQ8\
|
TkSuQmCC";
|
||||||
x9c9fHy9i/Ms73N/jn6lYDLAJxAnmG5YxBvEM5uWznmfOMLKkkJ8Tjxu0AWJH7kuu/zGueSwn2dG\
|
|
||||||
jGxmnjhCLJS6WO5iVjZU4inimKJqlO/Puaxw3uKsVuusfU/+wnBBW1nmOs0RpLCIJYgQIKOOCqqw\
|
static HTML_HEADER: &str = r###"<html>
|
||||||
EKdVI8VEhvaTHv5hxy+SSyZXBYwcC6hBheT4wf/gd7dmcXLCTQongd4X2/4YBYK7QKth29/Htt06\
|
<head>
|
||||||
AQLPwJXW8deawOwn6Y2OFjsCBraBi+uOJu8BlztA9EmXDMmRAjT9xSLwfkbflAeGboG+Nbe39j5O\
|
<link rel="stylesheet" href="/style.css">
|
||||||
H4AsdZW+AQ4OgbESZa97vDvU3du/Z9r9/QChS3K5hXof0gAAAAZiS0dEAP8A/wD/oL2nkwAAAAlw\
|
<script>
|
||||||
SFlzAAAuIwAALiMBeKU/dgAAAAd0SU1FB+cIEQMcKM7EsV8AAAA/SURBVBjTY2CgGfj//7/8////\
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
J/3///8NFE/6//+/PDaFk/5jgknYFL7BovANTJ6JWKchK1yGRX4Z+Z6hGgAAmotacR/hRugAAAAA\
|
folds = document.getElementsByClassName("fold");
|
||||||
SUVORK5CYII=";
|
for(i = 0; i < folds.length; i++) {
|
||||||
|
// folds[i].classList.add("open");
|
||||||
|
folds[i].addEventListener("click", (event) => {
|
||||||
|
folds[i].classList.add("open");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
"###;
|
||||||
|
|
||||||
|
static HTML_FOOTER: &str = r###"</body>
|
||||||
|
</html>
|
||||||
|
"###;
|
||||||
|
|
||||||
static EXPLANATION_TEXT: &str = "\
|
static EXPLANATION_TEXT: &str = "\
|
||||||
<h3>Chunks</h3>
|
<h3>Chunks</h3>
|
||||||
@ -64,6 +84,24 @@ static EXPLANATION_TEXT: &str = "\
|
|||||||
|
|
||||||
<p>Here we look at the metadata chunks. They contain the B-treesm which btrfs gets its name from. They are key-value stores for different kinds of information. For example, the filesystem tree stores which files and directories are in the filesystem, and the extent tree stores which areas of the disk are in use. Each B-tree consists of a number of 16KiB <b>nodes</b>, here symbolized by colorful boxes, with the color indicating which tree the node belongs to. Most of the nodes are <b>leaves</b>, which contain the actual key-value pairs. The others are <b>interior nodes</b>, and we indicate them with a little white circle. They are important to find the leaf a key is stored in.</p>";
|
<p>Here we look at the metadata chunks. They contain the B-treesm which btrfs gets its name from. They are key-value stores for different kinds of information. For example, the filesystem tree stores which files and directories are in the filesystem, and the extent tree stores which areas of the disk are in use. Each B-tree consists of a number of 16KiB <b>nodes</b>, here symbolized by colorful boxes, with the color indicating which tree the node belongs to. Most of the nodes are <b>leaves</b>, which contain the actual key-value pairs. The others are <b>interior nodes</b>, and we indicate them with a little white circle. They are important to find the leaf a key is stored in.</p>";
|
||||||
|
|
||||||
|
fn root_key_desc(id: u64) -> Option<&'static str> {
|
||||||
|
match id {
|
||||||
|
1 => Some("root"),
|
||||||
|
2 => Some("extent"),
|
||||||
|
3 => Some("chunk"),
|
||||||
|
4 => Some("device"),
|
||||||
|
5 => Some("filesystem"),
|
||||||
|
6 => Some("root directory"),
|
||||||
|
7 => Some("checksum"),
|
||||||
|
8 => Some("quota"),
|
||||||
|
9 => Some("UUID"),
|
||||||
|
10 => Some("free space"),
|
||||||
|
11 => Some("block group"),
|
||||||
|
0xffff_ffff_ffff_fff7 => Some("data reloc"),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn http_main_boxes(image: &[u8], _req: &Request) -> Response {
|
fn http_main_boxes(image: &[u8], _req: &Request) -> Response {
|
||||||
let mut treecolors: HashMap<u64, &str> = HashMap::new();
|
let mut treecolors: HashMap<u64, &str> = HashMap::new();
|
||||||
|
|
||||||
@ -266,6 +304,137 @@ fn http_main_boxes(image: &[u8], _req: &Request) -> Response {
|
|||||||
Response::html(result)
|
Response::html(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn html_item_row(key: Key, value: String, index: usize) -> String {
|
||||||
|
let even = if index % 2 == 0 { "even" } else { "odd" };
|
||||||
|
format!("<tr class = \"{even}\"><td>{:X}</td><td>{:?}</td><td>{:X}</td><td>{}</td></tr>\n",
|
||||||
|
key.key_id, key.key_type, key.key_offset, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn html_item_row_id_desc(key: Key, value: String, index: usize, desc: &str) -> String {
|
||||||
|
let even = if index % 2 == 0 { "even" } else { "odd" };
|
||||||
|
format!("<tr class = \"{even}\"><td>{}</td><td>{:?}</td><td>{:X}</td><td>{}</td></tr>\n",
|
||||||
|
desc, key.key_type, key.key_offset, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn item_value_string(tree_id: u64, key: Key, val: &Value) -> String {
|
||||||
|
match val {
|
||||||
|
Value::Root(_) => {
|
||||||
|
format!("<a href=\"/tree/{}\">go</a>", key.key_id)
|
||||||
|
},
|
||||||
|
Value::Dir(dir_item) =>
|
||||||
|
format!("name: {:?}, location: <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!("name: {:?}, location: <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) =>
|
||||||
|
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, len: {}", data.len()),
|
||||||
|
ExtentDataBody::External(ext_extent) =>
|
||||||
|
format!("external, disk_bytenr: {}, disk_num_bytes: {}, offset: {}, num_bytes: {}",
|
||||||
|
ext_extent.disk_bytenr,
|
||||||
|
ext_extent.disk_num_bytes,
|
||||||
|
ext_extent.offset,
|
||||||
|
ext_extent.num_bytes),
|
||||||
|
},
|
||||||
|
Value::Ref(ref_item) =>
|
||||||
|
format!("name: {:?}, index: {}",
|
||||||
|
&ref_item.name,
|
||||||
|
ref_item.index),
|
||||||
|
Value::Extent(extent_item) =>
|
||||||
|
format!("flags: {}, block_refs: {:?}", extent_item.flags, extent_item.block_refs),
|
||||||
|
Value::BlockGroup(blockgroup_item) =>
|
||||||
|
format!("used: {} bytes", 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!("{:?}", val);
|
||||||
|
String::new()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn http_tree_internal(tree: &Tree, tree_id: u64, key: Option<u64>) -> Response {
|
||||||
|
let mut table: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
table.push(String::from("<table><tbody>"));
|
||||||
|
|
||||||
|
let k = Key {key_id: key.unwrap_or(0), key_type: ItemType::Invalid, key_offset: 0};
|
||||||
|
let mut last_key_id: Option<u64> = None;
|
||||||
|
let mut key_index = 0;
|
||||||
|
|
||||||
|
for item in tree.range(k..).take(3000) {
|
||||||
|
if last_key_id.replace(item.key.key_id).filter(|x| *x != item.key.key_id).is_some() {
|
||||||
|
key_index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
match &item.value {
|
||||||
|
Value::Root(_) => {
|
||||||
|
let id = match root_key_desc(item.key.key_id) {
|
||||||
|
Some(desc) => format!("{:X} ({})", item.key.key_id, desc),
|
||||||
|
None => format!("{:X}", item.key.key_id),
|
||||||
|
};
|
||||||
|
let value = item_value_string(tree_id, item.key, &item.value);
|
||||||
|
table.push(html_item_row_id_desc(item.key, value, key_index, &id));
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
table.push(html_item_row(item.key, item_value_string(tree_id, item.key, &item.value), key_index));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.push(String::from("</tbody></table>"));
|
||||||
|
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
result.push_str(HTML_HEADER);
|
||||||
|
result.extend(table.iter().map(<String as AsRef<str>>::as_ref));
|
||||||
|
result.push_str(HTML_FOOTER);
|
||||||
|
|
||||||
|
Response::html(result)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn http_tree(image: &[u8], tree_id: &str, key: Option<&str>, _req: &Request) -> Response {
|
||||||
|
let key_id = key.and_then(|x|u64::from_str_radix(x, 16).ok());
|
||||||
|
let tree_id = u64::from_str(tree_id).unwrap();
|
||||||
|
let tree = if tree_id == 1 {
|
||||||
|
Tree::root(image).unwrap()
|
||||||
|
} else if tree_id == 3 {
|
||||||
|
Tree::chunk(image).unwrap()
|
||||||
|
} else {
|
||||||
|
Tree::new(image, tree_id).unwrap()
|
||||||
|
};
|
||||||
|
http_tree_internal(&tree, tree_id, key_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn http_root(image: &[u8], key: Option<&str>, _req: &Request) -> Response {
|
||||||
|
let key_id = key.and_then(|x|u64::from_str_radix(x, 16).ok());
|
||||||
|
let tree = Tree::root(image).unwrap();
|
||||||
|
http_tree_internal(&tree, 1, key_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----- Error handling -----
|
// ----- Error handling -----
|
||||||
|
|
||||||
pub struct MainError(String);
|
pub struct MainError(String);
|
||||||
|
52
style.css
Normal file
52
style.css
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
body {
|
||||||
|
padding: 0.2em 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
padding: 0.1em 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr:nth-child(even) {
|
||||||
|
}
|
||||||
|
|
||||||
|
table th {
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
table > tbody > tr.view {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
table > tbody > tr.even {
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
table > tbody > tr.fold {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table > tbody > tr.fold > td {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table > tbody > tr.fold.open {
|
||||||
|
display: table-row;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
table > tbody > tr.view td:first-child:before {
|
||||||
|
color: #999;
|
||||||
|
content: "x";
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
table > tbody > tr.view.open td:first-child:before {
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
*/
|
Loading…
Reference in New Issue
Block a user