switch to using maud for templating
This commit is contained in:
parent
38b1f3d040
commit
39def44579
@ -5,5 +5,6 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
binparse_derive = { path = "../binparse_derive" }
|
||||
maud = "0.26.0"
|
||||
memmap2 = "0.7.1"
|
||||
rouille = "3.6.2"
|
||||
|
@ -5,6 +5,7 @@ pub mod btrfs_lookup;
|
||||
pub mod addrmap;
|
||||
pub mod nodereader;
|
||||
pub mod http_tree;
|
||||
pub mod render_common;
|
||||
pub mod render_tree;
|
||||
pub mod main_error;
|
||||
|
||||
|
28
src/main.rs
28
src/main.rs
@ -272,31 +272,3 @@ fn http_main_boxes(image: &[u8], _req: &Request) -> Response {
|
||||
|
||||
Response::html(result)
|
||||
}
|
||||
|
||||
/*
|
||||
fn html_item_row(item: &Item, index: usize, tree_id: u64, highlight: bool, extend: bool) -> String {
|
||||
let value_string = item_value_string(tree_id, item.key, &item.value);
|
||||
let raw_string = format!("{:#?}", item);
|
||||
let even = if index % 2 == 0 { "even" } else { "odd" };
|
||||
let id_desc = row_id_desc(item.key, tree_id);
|
||||
format!("<tr class = \"{even}\"><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>\n<tr class=\"{even}\"><td colspan=4>{}</td></tr>\n",
|
||||
id_desc.0, id_desc.1, id_desc.2, &value_string, &raw_string)
|
||||
}
|
||||
|
||||
fn html_item_row_highlighted(key: Key, value: String, _index: usize, tree_id: u64) -> String {
|
||||
let id_desc = row_id_desc(key, tree_id);
|
||||
format!("<tr class = \"highlight\"><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>\n",
|
||||
id_desc.0, id_desc.1, id_desc.2, value)
|
||||
}
|
||||
|
||||
fn row_id_desc(key: Key, tree_id: u64) -> (String, String, String) {
|
||||
let x = format!("{:X}", key.key_id);
|
||||
let y = format!("{:?}", 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)
|
||||
}
|
||||
*/
|
||||
|
38
src/render_common.rs
Normal file
38
src/render_common.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use maud::Render;
|
||||
use std::fmt::{Debug, UpperHex};
|
||||
|
||||
pub struct DebugRender<T>(pub T);
|
||||
|
||||
impl<T: Debug> Render for DebugRender<T> {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
format_args!("{0:#?}", self.0).render_to(w);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Hex<T>(pub T);
|
||||
|
||||
impl<T: UpperHex> Render for Hex<T> {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
format_args!("{0:X}", self.0).render_to(w);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size_name(x: u64) -> String {
|
||||
if x == 0 {
|
||||
format!("0 B")
|
||||
} else if x % (1<<10) != 0 {
|
||||
format!("{} B", x)
|
||||
} else if x % (1<<20) != 0 {
|
||||
format!("{} KiB", x / (1<<10))
|
||||
} else if x % (1<<30) != 0 {
|
||||
format!("{} MiB", x / (1<<20))
|
||||
} else if x % (1<<40) != 0 {
|
||||
format!("{} GiB", x / (1<<30))
|
||||
} else if x % (1<<50) != 0 {
|
||||
format!("{} TiB", x / (1<<40))
|
||||
} else if x % (1<<60) != 0 {
|
||||
format!("{} PiB", x / (1<<50))
|
||||
} else {
|
||||
format!("{} EiB", x / (1<<60))
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
use crate::btrfs_structs::{Item, Key, ItemType, Value, ExtentDataBody};
|
||||
use crate::render_common::{DebugRender, Hex, size_name};
|
||||
use maud::{Markup, html, DOCTYPE, PreEscaped};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TableResult<'a> {
|
||||
@ -10,92 +12,124 @@ pub struct TableResult<'a> {
|
||||
pub last_key: Key,
|
||||
}
|
||||
|
||||
static HTML_HEADER: &str = r###"<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
"###;
|
||||
pub fn render_table(table: TableResult) -> Markup {
|
||||
|
||||
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));
|
||||
let header: String = if let Some(desc) = table.tree_desc {
|
||||
format!("Tree {} ({})", table.tree_id, desc)
|
||||
} else {
|
||||
result.push_str(&format!("<h1>Tree {}</h1>", table.tree_id));
|
||||
}
|
||||
format!("Tree {}", 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)));
|
||||
let first_key_url = format!("/tree/{}",
|
||||
table.tree_id);
|
||||
let prev_key_url = format!("/tree/{}/to/{:016X}-{:02X}-{:016X}",
|
||||
table.tree_id,
|
||||
table.first_key.key_id,
|
||||
u8::from(table.first_key.key_type),
|
||||
table.first_key.key_offset);
|
||||
let next_key_url = format!("/tree/{}/from/{:016X}-{:02X}-{:016X}",
|
||||
table.tree_id,
|
||||
table.last_key.key_id,
|
||||
u8::from(table.last_key.key_type),
|
||||
table.first_key.key_offset);
|
||||
let last_key_url = format!("/tree/{}/to/{:016X}-{:02X}-{:016X}",
|
||||
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 mut rows: Vec<Markup> = Vec::new();
|
||||
|
||||
for &(it, _it_data) in table.items.iter() {
|
||||
let highlighted = if table.key_id.filter(|x|*x == it.key.key_id).is_some() { "highlight" } else { "" };
|
||||
let value_string = item_value_string(table.tree_id, it);
|
||||
let details_string = item_details_string(table.tree_id, it);
|
||||
let raw_string = format!("{:#?}", &it.value);
|
||||
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);
|
||||
rows.push(html! {
|
||||
details.item.(highlighted) {
|
||||
summary {
|
||||
span.key.key_id.(key_type_class(it.key)) {
|
||||
(id_desc.0)
|
||||
}
|
||||
span.key.key_type.(key_type_class(it.key)) {
|
||||
(id_desc.1)
|
||||
}
|
||||
span.key.key_offset.(key_type_class(it.key)) {
|
||||
(id_desc.2)
|
||||
}
|
||||
span.itemvalue.(key_type_class(it.key)) {
|
||||
(&value_string)
|
||||
}
|
||||
}
|
||||
|
||||
div.details {
|
||||
(&details_string)
|
||||
|
||||
result.push_str(&row);
|
||||
details {
|
||||
summary {
|
||||
"show full value"
|
||||
}
|
||||
pre {
|
||||
(&raw_string)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
result.push_str("</tbody></table>\n");
|
||||
|
||||
result.push_str(HTML_FOOTER);
|
||||
// the complete page
|
||||
html! {
|
||||
(DOCTYPE)
|
||||
head {
|
||||
link rel="stylesheet" href="/style.css";
|
||||
}
|
||||
body {
|
||||
h1 {
|
||||
(header)
|
||||
}
|
||||
|
||||
result
|
||||
@if table.tree_id != 1 {
|
||||
a href="/tree/1" {
|
||||
"go back to root tree"
|
||||
}
|
||||
}
|
||||
|
||||
form method="get" action={"/tree/" (table.tree_id)} {
|
||||
input type="text" name="key" value=(key_input_value);
|
||||
input type="submit" value="Search";
|
||||
}
|
||||
|
||||
a.nav href=(first_key_url) { div.nav { "first" } }
|
||||
a.nav href=(prev_key_url) { div.nav { "prev" } }
|
||||
|
||||
@for row in &rows { (row) }
|
||||
|
||||
a.nav href=(next_key_url) { div.nav { "next" } }
|
||||
a.nav href=(last_key_url) { div.nav { "last" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn row_id_desc(key: Key, tree_id: u64) -> (String, String, String) {
|
||||
fn key_type_class(key: Key) -> &'static str {
|
||||
match 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",
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
fn row_id_desc(key: Key, tree_id: u64) -> (Markup, Markup, Markup) {
|
||||
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 {
|
||||
@ -103,59 +137,131 @@ fn row_id_desc(key: Key, tree_id: u64) -> (String, String, String) {
|
||||
} else {
|
||||
format!("{:X}", key.key_offset)
|
||||
};
|
||||
(x,y,z)
|
||||
(PreEscaped(x),PreEscaped(y),PreEscaped(z))
|
||||
}
|
||||
|
||||
fn item_value_string(tree_id: u64, key: Key, val: &Value) -> String {
|
||||
match val {
|
||||
fn item_value_string(tree_id: u64, item: &Item) -> Markup {
|
||||
match &item.value {
|
||||
Value::Root(_) => {
|
||||
format!("<a href=\"/tree/{}\">go to tree {}</a>", key.key_id, key.key_id)
|
||||
html! { a href={"/tree/" (item.key.key_id)} { "go to tree " (item.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::Dir(dir_item) | Value::DirIndex(dir_item) => {
|
||||
let name = format!("{:?}", &dir_item.name);
|
||||
let id = dir_item.location.key_id;
|
||||
html! {
|
||||
(name)
|
||||
" @ "
|
||||
a href=(format!("/tree/{tree_id}/{id:x}")) {
|
||||
(Hex(id))
|
||||
}
|
||||
}
|
||||
},
|
||||
Value::Inode(_) =>
|
||||
html! { "" },
|
||||
Value::ExtentData(extent_data_item) =>
|
||||
match &extent_data_item.data {
|
||||
ExtentDataBody::Inline(data) => format!("inline, length {}", data.len()),
|
||||
ExtentDataBody::Inline(data) =>
|
||||
PreEscaped(format!("inline, length {}", size_name(data.len() as u64))),
|
||||
ExtentDataBody::External(ext_extent) =>
|
||||
format!("external, length {}",
|
||||
ext_extent.num_bytes),
|
||||
PreEscaped(format!("external, length {}", size_name(ext_extent.num_bytes))),
|
||||
},
|
||||
Value::Ref(ref_item) =>
|
||||
format!("{:?}", &ref_item.name),
|
||||
html! { (format!("{:?}", &ref_item.name)) },
|
||||
Value::Extent(extent_item) =>
|
||||
format!("flags: {}, block_refs: {:?}", extent_item.flags, extent_item.block_refs),
|
||||
PreEscaped(format!("flags: {}, block_refs: {:?}", extent_item.flags, extent_item.block_refs)),
|
||||
Value::BlockGroup(blockgroup_item) =>
|
||||
format!("{} bytes used", blockgroup_item.used),
|
||||
PreEscaped(format!("{} used", size_name(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),
|
||||
PreEscaped(format!("chunk_tree: {}, chunk_offset: {:x}, length: {}", dev_extent_item.chunk_tree, dev_extent_item.chunk_offset, size_name(dev_extent_item.length))),
|
||||
Value::UUIDSubvol(uuid_subvol_item) =>
|
||||
format!("subvolume id: {}", uuid_subvol_item.subvol_id),
|
||||
PreEscaped(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),
|
||||
PreEscaped(format!("extent_count: {}, flags: {}", free_space_info.extent_count, free_space_info.flags)),
|
||||
Value::Dev(dev_item) =>
|
||||
format!("total_bytes: {}", dev_item.total_bytes),
|
||||
PreEscaped(format!("total_bytes: {}", size_name(dev_item.total_bytes))),
|
||||
Value::Chunk(chunk_item) =>
|
||||
format!("size: {}", chunk_item.size),
|
||||
PreEscaped(format!("size: {}", chunk_item.size)),
|
||||
_ => {
|
||||
println!("{:?} {:?}", key, val);
|
||||
String::new()
|
||||
// println!("{:?} {:?}", item.key, item.valu);
|
||||
PreEscaped(String::new())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn item_details_string(tree_id: u64, item: &Item) -> Markup {
|
||||
match &item.value {
|
||||
Value::Inode(inode_item) => {
|
||||
html! { table { tbody {
|
||||
tr { td { "size" } td { (inode_item.size) } }
|
||||
tr { td { "mode" } td { (inode_item.mode) } }
|
||||
tr { td { "uid" } td { (inode_item.uid) } }
|
||||
tr { td { "gid" } td { (inode_item.gid) } }
|
||||
tr { td { "nlink" } td { (inode_item.nlink) } }
|
||||
tr { td { "atime" } td { (inode_item.atime.sec) } }
|
||||
tr { td { "ctime" } td { (inode_item.ctime.sec) } }
|
||||
tr { td { "mtime" } td { (inode_item.mtime.sec) } }
|
||||
tr { td { "otime" } td { (inode_item.otime.sec) } }
|
||||
}}}
|
||||
},
|
||||
Value::ExtentData(extent_item) => {
|
||||
match &extent_item.data {
|
||||
ExtentDataBody::Inline(data) => {
|
||||
html! {} // we really want data as string / hex
|
||||
},
|
||||
ExtentDataBody::External(ext_extent) => {
|
||||
html! {
|
||||
p {
|
||||
@if ext_extent.disk_bytenr == 0 {
|
||||
(size_name(ext_extent.num_bytes)) " of zeros."
|
||||
} @ else {
|
||||
(format!("{} on disk, starting at offset {:X} within the extent at address {:X}; {} in the file starting from offset {:X}.", size_name(ext_extent.disk_num_bytes), ext_extent.offset, ext_extent.disk_bytenr, size_name(ext_extent.num_bytes), item.key.key_offset))
|
||||
}
|
||||
}
|
||||
table { tbody {
|
||||
tr { td { "compression" } td { (extent_item.header.compression) } }
|
||||
tr { td { "encryption" } td { (extent_item.header.encryption) } }
|
||||
tr { td { "other_encoding" } td { (extent_item.header.other_encoding) } }
|
||||
}}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
Value::Ref(ref_item) => {
|
||||
html! { table { tbody {
|
||||
tr { td { "name" } td { (format!("{:?}", ref_item.name)) } }
|
||||
tr { td { "index" } td { (ref_item.index) } }
|
||||
}}}
|
||||
},
|
||||
Value::Dir(dir_item) | Value::DirIndex(dir_item) => {
|
||||
html! { table { tbody {
|
||||
tr { td { "name" } td { (format!("{:?}", dir_item.name)) } }
|
||||
}}}
|
||||
},
|
||||
Value::Root(root_item) => {
|
||||
let inode = &root_item.inode;
|
||||
html! { table { tbody {
|
||||
tr { td { "root dir id" } td { (format!("{:X}", root_item.root_dirid)) } }
|
||||
tr { td { "logical address" } td { (format!("{:X}", root_item.bytenr)) } }
|
||||
tr { td { "bytes used" } td { (size_name(root_item.bytes_used)) } }
|
||||
tr { td { "last snapshot" } td { (root_item.last_snapshot) } }
|
||||
tr { td { "flags" } td { (root_item.flags) } }
|
||||
tr { td { "refs" } td { (root_item.refs) } }
|
||||
tr { td { "level" } td { (root_item.level) } }
|
||||
tr { td { "UUID" } td { (format!("{:?}", root_item.uuid)) } }
|
||||
tr { td { "parent UUID" } td { (format!("{:?}", root_item.parent_uuid)) } }
|
||||
tr { td { "received UUID" } td { (format!("{:?}", root_item.received_uuid)) } }
|
||||
tr { td { "ctransid" } td { (root_item.ctransid) } }
|
||||
tr { td { "otransid" } td { (root_item.otransid) } }
|
||||
tr { td { "stransid" } td { (root_item.stransid) } }
|
||||
tr { td { "rtransid" } td { (root_item.rtransid) } }
|
||||
tr { td { "ctime" } td { (root_item.ctime.sec) } }
|
||||
tr { td { "otime" } td { (root_item.otime.sec) } }
|
||||
tr { td { "stime" } td { (root_item.stime.sec) } }
|
||||
tr { td { "rtime" } td { (root_item.rtime.sec) } }
|
||||
}}}
|
||||
},
|
||||
_ => {
|
||||
html! {}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
40
style.css
40
style.css
@ -55,7 +55,20 @@ table > tbody > tr.view.open td:first-child:before {
|
||||
}
|
||||
*/
|
||||
|
||||
details {
|
||||
div.nav {
|
||||
padding: 5px;
|
||||
background-color: #dde;
|
||||
border-radius: 5px;
|
||||
margin: 5px 0;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a.nav {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
details.item {
|
||||
padding: 5px;
|
||||
background-color: #dde;
|
||||
border-radius: 5px;
|
||||
@ -103,16 +116,16 @@ details .key a {
|
||||
}
|
||||
|
||||
span.key_id {
|
||||
width: 160px;
|
||||
min-width: 160px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
span.key_type {
|
||||
width: 160px;
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
span.key_offset {
|
||||
width: 160px;
|
||||
min-width: 160px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@ -135,3 +148,22 @@ span.key_type.dir {
|
||||
span.key_type.root {
|
||||
background-color: #111;
|
||||
}
|
||||
|
||||
.details table {
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.details td {
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
.details td:first-child {
|
||||
border: 1px solid white;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.details p {
|
||||
padding: 0;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user