cdk-ldk web ui updates (#1027)

This commit is contained in:
Erik
2025-09-03 16:46:49 +02:00
committed by GitHub
parent 734e62b04a
commit 39f256a648
7 changed files with 840 additions and 335 deletions

View File

@@ -29,6 +29,3 @@ tower-http.workspace = true
rust-embed = "8.5.0"
serde_urlencoded = "0.7"
urlencoding = "2.1"

View File

@@ -213,7 +213,7 @@ pub async fn post_open_channel(
}
pub async fn close_channel_page(
State(_state): State<AppState>,
State(state): State<AppState>,
query: Query<HashMap<String, String>>,
) -> Result<Html<String>, StatusCode> {
let channel_id = query.get("channel_id").unwrap_or(&"".to_string()).clone();
@@ -229,24 +229,40 @@ pub async fn close_channel_page(
return Ok(Html(layout("Close Channel Error", content).into_string()));
}
// Get channel information for amount display
let channels = state.node.inner.list_channels();
let channel = channels
.iter()
.find(|c| c.user_channel_id.0.to_string() == channel_id);
let content = form_card(
"Close Channel",
html! {
p { "Are you sure you want to close this channel?" }
div class="info-item" {
span class="info-label" { "User Channel ID:" }
span class="info-value" style="font-family: monospace; font-size: 0.85rem;" { (channel_id) }
p style="margin-bottom: 1.5rem;" { "Are you sure you want to close this channel?" }
// Channel details in consistent format
div class="channel-details" {
div class="detail-row" {
span class="detail-label" { "User Channel ID" }
span class="detail-value-amount" { (channel_id) }
}
div class="detail-row" {
span class="detail-label" { "Node ID" }
span class="detail-value-amount" { (node_id) }
}
@if let Some(ch) = channel {
div class="detail-row" {
span class="detail-label" { "Channel Amount" }
span class="detail-value-amount" { (format_sats_as_btc(ch.channel_value_sats)) }
}
}
}
div class="info-item" {
span class="info-label" { "Node ID:" }
span class="info-value" style="font-family: monospace; font-size: 0.85rem;" { (node_id) }
}
form method="post" action="/channels/close" style="margin-top: 1rem;" {
form method="post" action="/channels/close" style="margin-top: 1rem; display: flex; justify-content: space-between; align-items: center;" {
input type="hidden" name="channel_id" value=(channel_id) {}
input type="hidden" name="node_id" value=(node_id) {}
button type="submit" style="background: #dc3545;" { "Close Channel" }
" "
a href="/balance" { button type="button" { "Cancel" } }
a href="/balance" { button type="button" class="button-secondary" { "Cancel" } }
button type="submit" class="button-destructive" { "Close Channel" }
}
},
);
@@ -255,7 +271,7 @@ pub async fn close_channel_page(
}
pub async fn force_close_channel_page(
State(_state): State<AppState>,
State(state): State<AppState>,
query: Query<HashMap<String, String>>,
) -> Result<Html<String>, StatusCode> {
let channel_id = query.get("channel_id").unwrap_or(&"".to_string()).clone();
@@ -273,32 +289,48 @@ pub async fn force_close_channel_page(
));
}
// Get channel information for amount display
let channels = state.node.inner.list_channels();
let channel = channels
.iter()
.find(|c| c.user_channel_id.0.to_string() == channel_id);
let content = form_card(
"Force Close Channel",
html! {
div style="border: 2px solid #d63384; background-color: rgba(214, 51, 132, 0.1); padding: 1rem; margin-bottom: 1rem; border-radius: 0.5rem;" {
h4 style="color: #d63384; margin: 0 0 0.5rem 0;" { "⚠️ Warning: Force Close" }
p style="color: #d63384; margin: 0; font-size: 0.9rem;" {
div style="border: 2px solid #f97316; background-color: rgba(249, 115, 22, 0.1); padding: 1rem; margin-bottom: 1rem; border-radius: 0.5rem;" {
h4 style="color: #f97316; margin: 0 0 0.5rem 0;" { "⚠️ Warning: Force Close" }
p style="color: #f97316; margin: 0; font-size: 0.9rem;" {
"Force close should NOT be used if normal close is preferred. "
"Force close will immediately broadcast the latest commitment transaction and may result in delayed fund recovery. "
"Only use this if the channel counterparty is unresponsive or there are other issues preventing normal closure."
}
}
p { "Are you sure you want to force close this channel?" }
div class="info-item" {
span class="info-label" { "User Channel ID:" }
span class="info-value" style="font-family: monospace; font-size: 0.85rem;" { (channel_id) }
p style="margin-bottom: 1.5rem;" { "Are you sure you want to force close this channel?" }
// Channel details in consistent format
div class="channel-details" {
div class="detail-row" {
span class="detail-label" { "User Channel ID" }
span class="detail-value-amount" { (channel_id) }
}
div class="detail-row" {
span class="detail-label" { "Node ID" }
span class="detail-value-amount" { (node_id) }
}
@if let Some(ch) = channel {
div class="detail-row" {
span class="detail-label" { "Channel Amount" }
span class="detail-value-amount" { (format_sats_as_btc(ch.channel_value_sats)) }
}
}
}
div class="info-item" {
span class="info-label" { "Node ID:" }
span class="info-value" style="font-family: monospace; font-size: 0.85rem;" { (node_id) }
}
form method="post" action="/channels/force-close" style="margin-top: 1rem;" {
form method="post" action="/channels/force-close" style="margin-top: 1rem; display: flex; justify-content: space-between; align-items: center;" {
input type="hidden" name="channel_id" value=(channel_id) {}
input type="hidden" name="node_id" value=(node_id) {}
button type="submit" style="background: #d63384;" { "Force Close Channel" }
" "
a href="/balance" { button type="button" { "Cancel" } }
a href="/balance" { button type="button" class="button-secondary" { "Cancel" } }
button type="submit" class="button-destructive" { "Force Close Channel" }
}
},
);

View File

@@ -3,7 +3,7 @@ use axum::http::StatusCode;
use axum::response::Html;
use maud::html;
use crate::web::handlers::AppState;
use crate::web::handlers::utils::AppState;
use crate::web::templates::{format_sats_as_btc, layout};
pub async fn balance_page(State(state): State<AppState>) -> Result<Html<String>, StatusCode> {
@@ -26,18 +26,35 @@ pub async fn balance_page(State(state): State<AppState>) -> Result<Html<String>,
html! {
h2 style="text-align: center; margin-bottom: 3rem;" { "Lightning" }
// Quick Actions section - matching dashboard style
// Quick Actions section - individual cards
div class="card" style="margin-bottom: 2rem;" {
h2 { "Quick Actions" }
div style="display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap;" {
a href="/channels/open" style="text-decoration: none; flex: 1; min-width: 200px;" {
button class="button-primary" style="width: 100%;" { "Open Channel" }
div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
// Open Channel Card
div class="quick-action-card" {
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Open Channel" }
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Create a new Lightning Network channel to connect with another node." }
a href="/channels/open" style="text-decoration: none;" {
button class="button-outline" { "Open Channel" }
}
}
a href="/invoices" style="text-decoration: none; flex: 1; min-width: 200px;" {
button class="button-primary" style="width: 100%;" { "Create Invoice" }
// Create Invoice Card
div class="quick-action-card" {
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Create Invoice" }
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a Lightning invoice to receive payments from other users or services." }
a href="/invoices" style="text-decoration: none;" {
button class="button-outline" { "Create Invoice" }
}
}
a href="/payments/send" style="text-decoration: none; flex: 1; min-width: 200px;" {
button class="button-primary" style="width: 100%;" { "Make Lightning Payment" }
// Make Payment Card
div class="quick-action-card" {
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Make Lightning Payment" }
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Lightning payments to other users using invoices. BOLT 11 & 12 supported." }
a href="/invoices" style="text-decoration: none;" {
button class="button-outline" { "Make Payment" }
}
}
}
}
@@ -73,18 +90,35 @@ pub async fn balance_page(State(state): State<AppState>) -> Result<Html<String>,
html! {
h2 style="text-align: center; margin-bottom: 3rem;" { "Lightning" }
// Quick Actions section - matching dashboard style
// Quick Actions section - individual cards
div class="card" style="margin-bottom: 2rem;" {
h2 { "Quick Actions" }
div style="display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap;" {
a href="/channels/open" style="text-decoration: none; flex: 1; min-width: 200px;" {
button class="button-primary" style="width: 100%;" { "Open Channel" }
div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
// Open Channel Card
div class="quick-action-card" {
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Open Channel" }
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Create a new Lightning channel by connecting with another node." }
a href="/channels/open" style="text-decoration: none;" {
button class="button-outline" { "Open Channel" }
}
}
a href="/invoices" style="text-decoration: none; flex: 1; min-width: 200px;" {
button class="button-primary" style="width: 100%;" { "Create Invoice" }
// Create Invoice Card
div class="quick-action-card" {
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Create Invoice" }
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a Lightning invoice to receive payments." }
a href="/invoices" style="text-decoration: none;" {
button class="button-outline" { "Create Invoice" }
}
}
a href="/payments/send" style="text-decoration: none; flex: 1; min-width: 200px;" {
button class="button-primary" style="width: 100%;" { "Make Lightning Payment" }
// Make Payment Card
div class="quick-action-card" {
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Make Lightning Payment" }
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Lightning payments to other users using invoices." }
a href="/payments/send" style="text-decoration: none;" {
button class="button-outline" { "Make Payment" }
}
}
}
}
@@ -112,57 +146,72 @@ pub async fn balance_page(State(state): State<AppState>) -> Result<Html<String>,
}
}
div class="card" {
h2 { "Channel Details" }
// Channel Details header (outside card)
h2 class="section-header" { "Channel Details" }
// Channels list
@for channel in &channels {
div class="channel-item" {
div class="channel-header" {
span class="channel-id" { "Channel ID: " (channel.channel_id.to_string()) }
// Channels list
@for (index, channel) in channels.iter().enumerate() {
@let node_id = channel.counterparty_node_id.to_string();
@let channel_number = index + 1;
div class="channel-box" {
// Channel number as prominent header
div class="channel-alias" { (format!("Channel {}", channel_number)) }
// Channel details in left-aligned format
div class="channel-details" {
div class="detail-row" {
span class="detail-label" { "Channel ID" }
span class="detail-value-amount" { (channel.channel_id.to_string()) }
}
@if let Some(short_channel_id) = channel.short_channel_id {
div class="detail-row" {
span class="detail-label" { "Short Channel ID" }
span class="detail-value-amount" { (short_channel_id.to_string()) }
}
}
div class="detail-row" {
span class="detail-label" { "Node ID" }
span class="detail-value-amount" { (node_id) }
}
div class="detail-row" {
span class="detail-label" { "Status" }
@if channel.is_usable {
span class="status-badge status-active" { "Active" }
} @else {
span class="status-badge status-inactive" { "Inactive" }
}
}
div class="info-item" {
span class="info-label" { "Counterparty" }
span class="info-value" style="font-family: monospace; font-size: 0.85rem;" { (channel.counterparty_node_id.to_string()) }
}
// Balance information cards (keeping existing style)
div class="balance-info" {
div class="balance-item" {
div class="balance-amount" { (format_sats_as_btc(channel.outbound_capacity_msat / 1000)) }
div class="balance-label" { "Outbound" }
}
@if let Some(short_channel_id) = channel.short_channel_id {
div class="info-item" {
span class="info-label" { "Short Channel ID" }
span class="info-value" { (short_channel_id.to_string()) }
}
div class="balance-item" {
div class="balance-amount" { (format_sats_as_btc(channel.inbound_capacity_msat / 1000)) }
div class="balance-label" { "Inbound" }
}
div class="balance-info" {
div class="balance-item" {
div class="balance-amount" { (format_sats_as_btc(channel.outbound_capacity_msat / 1000)) }
div class="balance-label" { "Outbound" }
}
div class="balance-item" {
div class="balance-amount" { (format_sats_as_btc(channel.inbound_capacity_msat / 1000)) }
div class="balance-label" { "Inbound" }
}
div class="balance-item" {
div class="balance-amount" { (format_sats_as_btc(channel.channel_value_sats)) }
div class="balance-label" { "Total" }
}
div class="balance-item" {
div class="balance-amount" { (format_sats_as_btc(channel.channel_value_sats)) }
div class="balance-label" { "Total" }
}
@if channel.is_usable {
div style="margin-top: 1rem; display: flex; gap: 0.5rem;" {
a href=(format!("/channels/close?channel_id={}&node_id={}", channel.user_channel_id.0, channel.counterparty_node_id)) {
button style="background: #dc3545;" { "Close Channel" }
}
a href=(format!("/channels/force-close?channel_id={}&node_id={}", channel.user_channel_id.0, channel.counterparty_node_id)) {
button style="background: #d63384;" title="Force close should not be used if normal close is preferred. Force close will broadcast the latest commitment transaction immediately." { "Force Close" }
}
}
// Action buttons
@if channel.is_usable {
div class="channel-actions" {
a href=(format!("/channels/close?channel_id={}&node_id={}", channel.user_channel_id.0, channel.counterparty_node_id)) {
button class="button-secondary" { "Close Channel" }
}
a href=(format!("/channels/force-close?channel_id={}&node_id={}", channel.user_channel_id.0, channel.counterparty_node_id)) {
button class="button-destructive" title="Force close should not be used if normal close is preferred. Force close will broadcast the latest commitment transaction immediately." { "Force Close" }
}
}
}
}
}
}
};

View File

@@ -79,15 +79,26 @@ pub async fn onchain_page(
let mut content = html! {
h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" }
// Quick Actions section - matching dashboard style
// Quick Actions section - individual cards
div class="card" style="margin-bottom: 2rem;" {
h2 { "Quick Actions" }
div style="display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap;" {
a href="/onchain?action=receive" style="text-decoration: none; flex: 1; min-width: 200px;" {
button class="button-primary" style="width: 100%;" { "Receive Bitcoin" }
div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
// Receive Bitcoin Card
div class="quick-action-card" {
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Receive Bitcoin" }
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a new Bitcoin address to receive on-chain payments from other users or services." }
a href="/onchain?action=receive" style="text-decoration: none;" {
button class="button-outline" { "Receive Bitcoin" }
}
}
a href="/onchain?action=send" style="text-decoration: none; flex: 1; min-width: 200px;" {
button class="button-primary" style="width: 100%;" { "Send Bitcoin" }
// Send Bitcoin Card
div class="quick-action-card" {
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Send Bitcoin" }
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Bitcoin to another address on the blockchain. Standard on-chain transactions." }
a href="/onchain?action=send" style="text-decoration: none;" {
button class="button-outline" { "Send Bitcoin" }
}
}
}
}
@@ -113,15 +124,26 @@ pub async fn onchain_page(
content = html! {
h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" }
// Quick Actions section - matching dashboard style
// Quick Actions section - individual cards
div class="card" style="margin-bottom: 2rem;" {
h2 { "Quick Actions" }
div style="display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap;" {
a href="/onchain?action=receive" style="text-decoration: none; flex: 1; min-width: 200px;" {
button class="button-primary" style="width: 100%;" { "Receive Bitcoin" }
div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
// Receive Bitcoin Card
div class="quick-action-card" {
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Receive Bitcoin" }
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a new Bitcoin address to receive on-chain payments from other users or services." }
a href="/onchain?action=receive" style="text-decoration: none;" {
button class="button-outline" { "Receive Bitcoin" }
}
}
a href="/onchain?action=send" style="text-decoration: none; flex: 1; min-width: 200px;" {
button class="button-primary" style="width: 100%;" { "Send Bitcoin" }
// Send Bitcoin Card
div class="quick-action-card" {
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Send Bitcoin" }
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Bitcoin to another address on the blockchain. Standard on-chain transactions." }
a href="/onchain?action=send" style="text-decoration: none;" {
button class="button-outline" { "Send Bitcoin" }
}
}
}
}
@@ -141,7 +163,7 @@ pub async fn onchain_page(
}
input type="hidden" id="send_action" name="send_action" value="send" {}
div style="display: flex; justify-content: space-between; gap: 1rem; margin-top: 2rem;" {
a href="/onchain" { button type="button" { "Cancel" } }
a href="/onchain" { button type="button" class="button-secondary" { "Cancel" } }
div style="display: flex; gap: 0.5rem;" {
button type="submit" onclick="document.getElementById('send_action').value='send'" { "Send Payment" }
button type="submit" onclick="document.getElementById('send_action').value='send_all'; document.getElementById('amount_sat').value=''" { "Send All" }
@@ -171,15 +193,26 @@ pub async fn onchain_page(
content = html! {
h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" }
// Quick Actions section - matching dashboard style
// Quick Actions section - individual cards
div class="card" style="margin-bottom: 2rem;" {
h2 { "Quick Actions" }
div style="display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap;" {
a href="/onchain?action=receive" style="text-decoration: none; flex: 1; min-width: 200px;" {
button class="button-primary" style="width: 100%;" { "Receive Bitcoin" }
div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
// Receive Bitcoin Card
div class="quick-action-card" {
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Receive Bitcoin" }
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a new Bitcoin address to receive on-chain payments from other users or services." }
a href="/onchain?action=receive" style="text-decoration: none;" {
button class="button-outline" { "Receive Bitcoin" }
}
}
a href="/onchain?action=send" style="text-decoration: none; flex: 1; min-width: 200px;" {
button class="button-primary" style="width: 100%;" { "Send Bitcoin" }
// Send Bitcoin Card
div class="quick-action-card" {
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Send Bitcoin" }
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Bitcoin to another address on the blockchain. Standard on-chain transactions." }
a href="/onchain?action=send" style="text-decoration: none;" {
button class="button-outline" { "Send Bitcoin" }
}
}
}
}
@@ -191,7 +224,7 @@ pub async fn onchain_page(
form method="post" action="/onchain/new-address" {
p style="margin-bottom: 2rem;" { "Click the button below to generate a new Bitcoin address for receiving on-chain payments." }
div style="display: flex; justify-content: space-between; gap: 1rem;" {
a href="/onchain" { button type="button" { "Cancel" } }
a href="/onchain" { button type="button" class="button-secondary" { "Cancel" } }
button class="button-primary" type="submit" { "Generate New Address" }
}
}
@@ -345,7 +378,7 @@ pub async fn onchain_confirm_page(
div class="card" {
div style="display: flex; justify-content: space-between; gap: 1rem; margin-top: 2rem;" {
a href="/onchain?action=send" {
button type="button" class="button-secondary" { "Cancel" }
button type="button" class="button-secondary" { "Cancel" }
}
div style="display: flex; gap: 0.5rem;" {
a href=(confirmation_url) {

View File

@@ -58,36 +58,261 @@ pub fn layout(title: &str, content: Markup) -> Markup {
--header-subtitle: #333333;
}
/* Dark mode using system preference */
/* Dark mode using system preference */
@media (prefers-color-scheme: dark) {
:root {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 84% 4.9%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
body {
background: linear-gradient(rgb(23, 25, 29), rgb(18, 19, 21));
}
/* Dark mode colors */
--fg-primary: #f8fafc;
--fg-muted: #94a3b8;
:root {
--background: 0 0% 0%;
--foreground: 0 0% 100%;
--card: 0 0% 0%;
--card-foreground: 0 0% 100%;
--popover: 0 0% 0%;
--popover-foreground: 0 0% 100%;
--primary: 0 0% 100%;
--primary-foreground: 0 0% 0%;
--secondary: 0 0% 20%;
--secondary-foreground: 0 0% 100%;
--muted: 0 0% 20%;
--muted-foreground: 0 0% 70%;
--accent: 0 0% 20%;
--accent-foreground: 0 0% 100%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 100%;
--border: 0 0% 20%;
--input: 0 0% 20%;
--ring: 0 0% 83.9%;
/* Dark mode text hierarchy colors */
--text-primary: #ffffff;
--text-secondary: #e6e6e6;
--text-tertiary: #cccccc;
--text-quaternary: #b3b3b3;
--text-muted: #999999;
--text-muted-2: #888888;
--text-muted-3: #666666;
--text-muted-4: #333333;
--text-subtle: #1a1a1a;
/* Header text colors for dark mode */
--header-title: #ffffff;
--header-subtitle: #e2e8f0;
--header-subtitle: #e6e6e6;
}
/* Dark mode box styling - no borders, subtle background */
.card {
background-color: rgba(255, 255, 255, 0.03) !important;
border: none !important;
}
.channel-box {
background-color: rgba(255, 255, 255, 0.03) !important;
border: none !important;
}
.metric-card {
background-color: rgba(255, 255, 255, 0.03) !important;
border: none !important;
}
.balance-item {
background-color: rgba(255, 255, 255, 0.03) !important;
border: none !important;
}
.node-info-main-container {
background-color: rgba(255, 255, 255, 0.03) !important;
border: none !important;
}
.node-avatar {
background-color: rgba(255, 255, 255, 0.03) !important;
border: none !important;
}
/* Text hierarchy colors */
.section-header {
color: var(--text-primary) !important;
}
.channel-alias {
color: var(--text-primary) !important;
}
.detail-label {
color: var(--text-muted) !important;
}
.detail-value, .detail-value-amount {
color: var(--text-secondary) !important;
}
.metric-label, .balance-label {
color: var(--text-muted) !important;
}
.metric-value, .balance-amount {
color: var(--text-primary) !important;
}
/* Page headers and section titles */
h1, h2, h3, h4, h5, h6 {
color: var(--text-primary) !important;
}
/* Form card titles */
.form-card h2, .form-card h3 {
color: var(--text-primary) !important;
}
/* Quick action cards styling */
.quick-action-card {
background-color: rgba(255, 255, 255, 0.03) !important;
border: none !important;
border-radius: 0.75rem !important;
padding: 1.5rem !important;
}
/* Dark mode outline button styling */
.button-outline {
background-color: transparent !important;
color: var(--text-primary) !important;
border: 1px solid var(--text-muted) !important;
}
.button-outline:hover {
background-color: rgba(255, 255, 255, 0.2) !important;
}
/* Navigation dark mode styling */
nav {
background-color: transparent !important;
border-top: none !important;
border-bottom: none !important;
}
}
/* New Header Layout Styles */
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0;
}
.header-left {
display: flex;
align-items: center;
gap: 1rem;
}
.header-avatar {
flex-shrink: 0;
background-color: hsl(var(--muted) / 0.3);
border: 1px solid hsl(var(--border));
border-radius: var(--radius);
padding: 0.75rem;
display: flex;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
}
.header-avatar-image {
width: 48px;
height: 48px;
border-radius: calc(var(--radius) - 2px);
object-fit: cover;
display: block;
}
.node-info {
display: flex;
flex-direction: column;
gap: 0.25rem;
padding-top: 0;
margin-top: 0;
}
.node-status {
display: flex;
align-items: center;
gap: 0.5rem;
}
.status-indicator {
width: 0.75rem;
height: 0.75rem;
border-radius: 50%;
background-color: #10b981;
box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2);
}
.status-text {
font-size: 0.875rem;
font-weight: 500;
color: #10b981;
}
.node-title {
font-size: 1.875rem;
font-weight: 600;
color: var(--header-title);
margin: 0;
line-height: 1.1;
}
.node-subtitle {
font-size: 0.875rem;
color: var(--header-subtitle);
font-weight: 500;
}
.header-right {
display: flex;
align-items: center;
}
/* Responsive header */
@media (max-width: 768px) {
.header-content {
flex-direction: column;
gap: 1rem;
text-align: center;
}
.header-left {
flex-direction: column;
text-align: center;
}
.node-title {
font-size: 1.5rem;
}
}
nav a {
color: var(--text-muted) !important;
}
nav a:hover {
color: var(--text-secondary) !important;
background-color: rgba(255, 255, 255, 0.05) !important;
}
nav a.active {
color: var(--text-primary) !important;
background-color: rgba(255, 255, 255, 0.08) !important;
}
nav a.active:hover {
background-color: rgba(255, 255, 255, 0.1) !important;
}
}
@@ -135,13 +360,13 @@ pub fn layout(title: &str, content: Markup) -> Markup {
background-position: center;
background-repeat: no-repeat;
border-bottom: 1px solid hsl(var(--border));
margin-bottom: 3rem;
text-align: center;
margin-bottom: 2rem;
text-align: left;
width: 100%;
height: 400px; /* Fixed height for better proportion */
height: 200px; /* Reduced height for more compact header */
display: flex;
align-items: center;
justify-content: center;
justify-content: flex-start;
}
/* Dark mode header background - using different image */
@@ -153,14 +378,17 @@ pub fn layout(title: &str, content: Markup) -> Markup {
/* Ensure text is positioned properly */
header .container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
position: relative;
top: auto;
left: auto;
transform: none;
z-index: 2;
width: 100%;
max-width: 1200px;
padding: 0 2rem;
display: flex;
align-items: center;
justify-content: flex-start;
}
h1 {
@@ -183,7 +411,7 @@ pub fn layout(title: &str, content: Markup) -> Markup {
@media (max-width: 768px) {
header {
height: 300px; /* Smaller height on mobile */
height: 150px; /* Smaller height on mobile */
}
header .container {
@@ -424,14 +652,17 @@ pub fn layout(title: &str, content: Markup) -> Markup {
}
.button-destructive {
background-color: hsl(var(--destructive));
color: hsl(var(--destructive-foreground));
background-color: transparent !important;
color: #DC2626 !important;
border: 1px solid #DC2626 !important;
}
.button-destructive:hover {
background-color: hsl(var(--destructive) / 0.9);
background-color: rgba(220, 38, 38, 0.2) !important;
}
.button-sm {
height: 2rem;
border-radius: calc(var(--radius) - 4px);
@@ -694,7 +925,7 @@ pub fn layout(title: &str, content: Markup) -> Markup {
border: 1px solid hsl(var(--border));
}
/* Legacy status classes */
/* Status badge classes - consistent with payment type badges */
.status-badge {
display: inline-flex;
align-items: center;
@@ -706,16 +937,24 @@ pub fn layout(title: &str, content: Markup) -> Markup {
}
.status-active {
background-color: hsl(142.1 70.6% 45.3%);
color: hsl(355.7 78% 98.4%);
background-color: hsl(142.1 70.6% 45.3% / 0.1);
color: hsl(142.1 70.6% 45.3%);
border: 1px solid hsl(142.1 70.6% 45.3% / 0.2);
}
.status-inactive {
background-color: hsl(var(--destructive));
color: hsl(var(--destructive-foreground));
background-color: hsl(0 84.2% 60.2% / 0.1);
color: hsl(0 84.2% 60.2%);
border: 1px solid hsl(0 84.2% 60.2% / 0.2);
}
.channel-item {
.status-pending {
background-color: hsl(215.4 16.3% 46.9% / 0.1);
color: hsl(215.4 16.3% 46.9%);
border: 1px solid hsl(215.4 16.3% 46.9% / 0.2);
}
.channel-box {
background-color: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: var(--radius);
@@ -723,23 +962,87 @@ pub fn layout(title: &str, content: Markup) -> Markup {
margin-bottom: 1.5rem;
}
.channel-header {
display: flex;
justify-content: space-between;
align-items: center;
.section-header {
font-size: 1.25rem;
font-weight: 700;
color: hsl(var(--foreground));
margin-bottom: 1.5rem;
line-height: 1.2;
}
.channel-alias {
font-size: 1.25rem;
font-weight: 600;
color: hsl(var(--foreground));
margin-bottom: 1rem;
line-height: 1.2;
}
.channel-details {
margin-bottom: 1.5rem;
}
.detail-row {
display: flex;
align-items: baseline;
margin-bottom: 0.75rem;
gap: 1rem;
}
.channel-id {
.detail-row:last-child {
margin-bottom: 0;
}
.detail-label {
font-weight: 500;
color: hsl(var(--muted-foreground));
font-size: 0.875rem;
min-width: 120px;
flex-shrink: 0;
}
.detail-value {
color: hsl(var(--foreground));
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Courier New', monospace;
font-size: 0.875rem;
color: hsl(var(--muted-foreground));
word-break: break-all;
flex: 1;
min-width: 0;
}
.detail-value-amount {
color: hsl(var(--foreground));
font-size: 0.875rem;
word-break: break-all;
flex: 1;
min-width: 0;
}
.channel-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 1rem;
gap: 1rem;
}
@media (max-width: 640px) {
.channel-actions {
flex-direction: column;
align-items: stretch;
}
.detail-row {
flex-direction: column;
align-items: flex-start;
gap: 0.25rem;
}
.detail-label {
min-width: auto;
}
}
.balance-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
@@ -908,6 +1211,30 @@ pub fn layout(title: &str, content: Markup) -> Markup {
border-color: hsl(var(--primary));
}
/* Dark mode specific styling for payment filter tabs */
@media (prefers-color-scheme: dark) {
.payment-filter-tab {
background-color: rgba(255, 255, 255, 0.03) !important;
border-color: var(--text-muted) !important;
color: var(--text-muted) !important;
}
.payment-filter-tab:hover {
background-color: rgba(255, 255, 255, 0.08) !important;
color: var(--text-secondary) !important;
}
.payment-filter-tab.active {
background-color: rgba(255, 255, 255, 0.12) !important;
color: var(--text-primary) !important;
border-color: var(--text-secondary) !important;
}
.payment-filter-tab.active:hover {
background-color: rgba(255, 255, 255, 0.15) !important;
}
}
.payment-type-badge {
display: inline-flex;
align-items: center;
@@ -1086,7 +1413,7 @@ pub fn layout(title: &str, content: Markup) -> Markup {
display: flex;
gap: 1.5rem;
margin-bottom: 1.5rem;
align-items: flex-start;
align-items: stretch;
}
.node-info-main-container {
@@ -1099,6 +1426,7 @@ pub fn layout(title: &str, content: Markup) -> Markup {
border-radius: var(--radius);
padding: 1.5rem;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
height: 100%;
}
.node-info-left {
@@ -1170,6 +1498,7 @@ pub fn layout(title: &str, content: Markup) -> Markup {
width: 280px;
display: flex;
flex-direction: column;
align-self: stretch;
}
.node-metrics .card {
@@ -1177,16 +1506,28 @@ pub fn layout(title: &str, content: Markup) -> Markup {
flex: 1;
display: flex;
flex-direction: column;
align-self: stretch;
}
.node-metrics .metrics-container {
flex-direction: column;
margin: 1rem 0 0 0;
flex: 1;
display: flex;
justify-content: flex-start;
gap: 1rem;
align-items: stretch;
}
.node-metrics .metric-card {
min-width: auto;
padding: 1rem;
height: fit-content;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
/* Mobile responsive design for node info */
@@ -1300,19 +1641,70 @@ pub fn layout(title: &str, content: Markup) -> Markup {
body {
header {
div class="container" {
h1 { "CDK LDK Node" }
p class="subtitle" { "Lightning Network Node Management" }
div class="header-content" {
div class="header-left" {
div class="header-avatar" {
img src="/static/images/nut.png" alt="CDK LDK Node Icon" class="header-avatar-image";
}
div class="node-info" {
div class="node-status" {
span class="status-indicator status-running" {}
span class="status-text" { "Running" }
}
h1 class="node-title" { "CDK LDK Node" }
span class="node-subtitle" { "Cashu Mint & Lightning Network Node Management" }
}
}
div class="header-right" {
// Right side content can be added here later if needed
}
}
}
}
nav {
div class="container" {
ul {
li { a href="/" { "Dashboard" } }
li { a href="/balance" { "Lightning" } }
li { a href="/onchain" { "On-chain" } }
li { a href="/invoices" { "Invoices" } }
li { a href="/payments" { "All Payments" } }
li {
a href="/" {
svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 0.5rem;" {
path d="M15.6 2.7a10 10 0 1 0 5.7 5.7" {}
circle cx="12" cy="12" r="2" {}
path d="M13.4 10.6 19 5" {}
}
"Dashboard"
}
}
li {
a href="/balance" {
svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 0.5rem;" {
path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z" {}
}
"Lightning"
}
}
li {
a href="/onchain" {
svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 0.5rem;" {
path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" {}
path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" {}
}
"On-chain"
}
}
li {
a href="/payments" {
svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 0.5rem;" {
path d="M12 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5" {}
path d="m16 19 3 3 3-3" {}
path d="M18 12h.01" {}
path d="M19 16v6" {}
path d="M6 12h.01" {}
circle cx="12" cy="12" r="2" {}
}
"All Payments"
}
}
}
}
}
@@ -1320,6 +1712,8 @@ pub fn layout(title: &str, content: Markup) -> Markup {
main class="container" {
(content)
}
}
}
}

View File

@@ -17,7 +17,7 @@ pub fn payment_list_item(
let status_class = match status {
"Succeeded" => "status-active",
"Failed" => "status-inactive",
"Pending" => "status-badge",
"Pending" => "status-pending",
_ => "status-badge",
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 43 KiB