Add base project
Some checks failed
Deploy to VPS / deploy (push) Failing after 5s

This commit is contained in:
2025-11-06 19:42:12 +01:00
parent 29c60e4824
commit c0688ca363
8 changed files with 504 additions and 33 deletions

237
Cargo.lock generated
View File

@@ -2,18 +2,52 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.10.0" version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "cassowary"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "castaway"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
dependencies = [
"rustversion",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "compact_str"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
dependencies = [
"castaway",
"cfg-if",
"itoa",
"ryu",
"static_assertions",
]
[[package]] [[package]]
name = "crossterm" name = "crossterm"
version = "0.27.0" version = "0.27.0"
@@ -39,6 +73,65 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.177" version = "0.2.177"
@@ -60,6 +153,15 @@ version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "lru"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
dependencies = [
"hashbrown",
]
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.11" version = "0.8.11"
@@ -95,6 +197,50 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ratatui"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef"
dependencies = [
"bitflags",
"cassowary",
"compact_str",
"crossterm",
"itertools 0.12.1",
"lru",
"paste",
"stability",
"strum",
"unicode-segmentation",
"unicode-truncate",
"unicode-width",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.18" version = "0.5.18"
@@ -104,6 +250,18 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@@ -151,8 +309,87 @@ name = "ssh-tui"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"crossterm", "crossterm",
"ratatui",
] ]
[[package]]
name = "stability"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strum"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "syn"
version = "2.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-truncate"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
dependencies = [
"itertools 0.13.0",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.1+wasi-snapshot-preview1" version = "0.11.1+wasi-snapshot-preview1"

View File

@@ -5,7 +5,8 @@ edition = "2021"
[dependencies] [dependencies]
crossterm = "0.27" crossterm = "0.27"
ratatui = "0.26"
[[bin]] [[bin]]
name = "ssh-tui" name = "ssh-tui"
path = "main.rs" path = "src/main.rs"

32
main.rs
View File

@@ -1,32 +0,0 @@
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use std::io::{self};
fn main() -> Result<(), Box<dyn std::error::Error>> {
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
execute!(
stdout,
crossterm::terminal::Clear(crossterm::terminal::ClearType::All)
)?;
execute!(stdout, crossterm::cursor::MoveTo(0, 0))?;
println!("Wellcome to dcorral.com!");
execute!(stdout, crossterm::cursor::MoveTo(0, 1))?;
println!("Press any key to exit.");
loop {
if let Event::Key(_) = event::read()? {
break;
}
}
disable_raw_mode()?;
execute!(stdout, LeaveAlternateScreen, DisableMouseCapture)?;
Ok(())
}

41
src/app.rs Normal file
View File

@@ -0,0 +1,41 @@
use crate::theme::Theme;
pub const NUM_BOXES: usize = 4;
pub struct App {
pub focus: usize,
pub theme: Theme,
pub show_help: bool,
pub show_welcome: bool,
}
impl App {
pub fn new() -> Self {
Self {
focus: 0,
theme: Theme::gruvbox_dark(),
show_help: false,
show_welcome: true,
}
}
pub fn move_left(&mut self) {
self.focus = (self.focus + 3) % NUM_BOXES;
}
pub fn move_right(&mut self) {
self.focus = (self.focus + 1) % NUM_BOXES;
}
pub fn move_down(&mut self) {
if self.focus < 2 {
self.focus += 2;
} else {
self.focus -= 2;
}
}
pub fn move_up(&mut self) {
self.move_down(); // symmetric
}
}

35
src/input.rs Normal file
View File

@@ -0,0 +1,35 @@
use crossterm::event::{Event, KeyCode};
use crate::app::App;
pub fn handle_input(app: &mut App, event: Event) -> bool {
if app.show_welcome {
if let Event::Key(_) = event {
app.show_welcome = false;
}
} else if app.show_help {
if let Event::Key(key) = event {
match key.code {
KeyCode::Char('q')
| KeyCode::Esc
| KeyCode::Char('?')
| KeyCode::Enter
| KeyCode::Backspace => app.show_help = false,
_ => {}
}
}
} else {
if let Event::Key(key) = event {
match key.code {
KeyCode::Char('h') | KeyCode::Left => app.move_left(),
KeyCode::Char('l') | KeyCode::Right => app.move_right(),
KeyCode::Char('j') | KeyCode::Down => app.move_down(),
KeyCode::Char('k') | KeyCode::Up => app.move_up(),
KeyCode::Char('?') => app.show_help = true,
KeyCode::Char('q') | KeyCode::Esc => return false,
_ => {}
}
}
}
true
}

49
src/main.rs Normal file
View File

@@ -0,0 +1,49 @@
mod app;
mod input;
mod theme;
mod ui;
use crossterm::{
event, execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{backend::CrosstermBackend, Terminal};
use std::io::stdout;
fn main() -> Result<(), Box<dyn std::error::Error>> {
enable_raw_mode()?;
let (width, height) = crossterm::terminal::size()?;
if width < 15 || height < 15 {
disable_raw_mode()?;
println!("Your console is too small.");
return Ok(());
}
let mut stdout = stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let mut app = app::App::new();
let mut blink_counter = 0;
loop {
let blink = (blink_counter / 3) % 2 == 0;
terminal.draw(|f| ui::draw(f, &app, blink))?;
blink_counter += 1;
let timeout = if app.show_welcome {
std::time::Duration::from_millis(70)
} else {
std::time::Duration::from_millis(60)
};
if event::poll(timeout)? {
let event = event::read()?;
if !input::handle_input(&mut app, event) {
break;
}
}
}
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
Ok(())
}

32
src/theme.rs Normal file
View File

@@ -0,0 +1,32 @@
use ratatui::style::Color;
#[allow(dead_code)]
pub struct Theme {
pub bg: Color,
pub fg: Color,
pub red: Color,
pub green: Color,
pub yellow: Color,
pub blue: Color,
pub purple: Color,
pub aqua: Color,
pub orange: Color,
pub gray: Color,
}
impl Theme {
pub fn gruvbox_dark() -> Self {
Self {
bg: Color::Rgb(40, 40, 40),
fg: Color::Rgb(235, 219, 178),
red: Color::Rgb(204, 36, 29),
green: Color::Rgb(152, 151, 26),
yellow: Color::Rgb(215, 153, 33),
blue: Color::Rgb(69, 133, 136),
purple: Color::Rgb(177, 98, 134),
aqua: Color::Rgb(104, 157, 106),
orange: Color::Rgb(214, 93, 14),
gray: Color::Rgb(146, 131, 116),
}
}
}

108
src/ui.rs Normal file
View File

@@ -0,0 +1,108 @@
use ratatui::{
layout::{Alignment, Constraint, Direction, Layout},
style::Style,
text::{Line, Span},
widgets::{Block, Borders, Paragraph},
Frame,
};
use crate::app::App;
pub fn draw(f: &mut Frame, app: &App, blink: bool) {
let bg_block = Block::default().style(Style::default().bg(app.theme.bg));
f.render_widget(bg_block, f.size());
if app.show_welcome {
let fg = app.theme.fg;
let accent_fg = if blink { app.theme.blue } else { app.theme.bg };
let size = f.size();
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Percentage(40),
Constraint::Percentage(20),
Constraint::Percentage(40),
]
.as_ref(),
)
.split(size);
let welcome_text = Paragraph::new(vec![
Line::from(vec![
Span::styled("Welcome to ", Style::default().fg(fg).bg(app.theme.bg)),
Span::styled(
"dcorral.com",
Style::default().fg(app.theme.orange).bg(app.theme.bg),
),
Span::styled("!", Style::default().fg(fg).bg(app.theme.bg)),
]),
Line::from(vec![
Span::styled("press ", Style::default().fg(fg).bg(app.theme.bg)),
Span::styled("any key", Style::default().fg(accent_fg).bg(app.theme.bg)),
Span::styled(" to continue", Style::default().fg(fg).bg(app.theme.bg)),
]),
])
.alignment(Alignment::Center);
f.render_widget(welcome_text, chunks[1]);
} else if app.show_help {
let help_content = "Navigation:\n\
h / ←: Move left\n\
l / →: Move right\n\
j / ↓: Move down\n\
k / ↑: Move up\n\
? : Show help\n\
q / Esc: Quit";
let help_text = Paragraph::new(help_content)
.alignment(ratatui::layout::Alignment::Center)
.style(Style::default().fg(app.theme.fg).bg(app.theme.bg));
f.render_widget(help_text, f.size());
} else {
let size = f.size();
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(1), Constraint::Length(1)].as_ref())
.split(size);
let box_area = chunks[0];
let box_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(box_area);
let top_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(box_chunks[0]);
let bottom_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(box_chunks[1]);
let areas = [
top_chunks[0],
top_chunks[1],
bottom_chunks[0],
bottom_chunks[1],
];
for (i, area) in areas.iter().enumerate() {
let (bg, fg) = if app.focus == i {
(app.theme.blue, app.theme.bg)
} else {
(app.theme.bg, app.theme.fg)
};
let block = Block::default()
.title(format!("Box {}", i))
.borders(Borders::ALL)
.style(Style::default().bg(bg).fg(fg));
f.render_widget(block, *area);
}
let help_text = Paragraph::new("Use hjkl or arrows to navigate, ? for help, q to quit")
.style(Style::default().fg(app.theme.fg).bg(app.theme.bg));
f.render_widget(help_text, chunks[1]);
}
}