This commit is contained in:
RainBus
2024-10-25 17:51:17 +08:00
parent 83ff3fb6c4
commit f1ec0f2b67
18 changed files with 880 additions and 107 deletions

407
Cargo.lock generated
View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@@ -35,6 +35,51 @@ version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]]
name = "async-channel"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
dependencies = [
"concurrent-queue",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-fs"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
dependencies = [
"async-lock",
"autocfg",
"blocking",
"futures-lite 1.13.0",
]
[[package]]
name = "async-lock"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
dependencies = [
"event-listener 2.5.3",
]
[[package]]
name = "async-task"
version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
version = "1.3.0"
@@ -62,6 +107,19 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "blocking"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
dependencies = [
"async-channel",
"async-task",
"futures-io",
"futures-lite 2.3.0",
"piper",
]
[[package]]
name = "bytes"
version = "1.7.1"
@@ -103,6 +161,21 @@ dependencies = [
"static_assertions",
]
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "crossterm"
version = "0.28.1"
@@ -138,12 +211,47 @@ dependencies = [
"powerfmt",
]
[[package]]
name = "dirs"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.9"
@@ -154,6 +262,48 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "event-listener"
version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
dependencies = [
"event-listener 5.3.1",
"pin-project-lite",
]
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]]
name = "fastrand"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
[[package]]
name = "futures"
version = "0.3.30"
@@ -202,6 +352,31 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-lite"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
"fastrand 1.9.0",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]]
name = "futures-lite"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
dependencies = [
"futures-core",
"pin-project-lite",
]
[[package]]
name = "futures-macro"
version = "0.3.30"
@@ -243,12 +418,29 @@ dependencies = [
"slab",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.5"
@@ -259,6 +451,12 @@ dependencies = [
"allocator-api2",
]
[[package]]
name = "hashbrown"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
[[package]]
name = "heck"
version = "0.5.0"
@@ -271,6 +469,26 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
]
[[package]]
name = "indexmap"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [
"equivalent",
"hashbrown 0.15.0",
]
[[package]]
name = "instability"
version = "0.3.2"
@@ -281,6 +499,15 @@ dependencies = [
"syn",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "itertools"
version = "0.13.0"
@@ -296,12 +523,28 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "libredox"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags",
"libc",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
@@ -330,7 +573,7 @@ version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904"
dependencies = [
"hashbrown",
"hashbrown 0.14.5",
]
[[package]]
@@ -391,6 +634,12 @@ version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe"
[[package]]
name = "parking"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "parking_lot"
version = "0.12.3"
@@ -432,6 +681,26 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "piper"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
dependencies = [
"atomic-waker",
"fastrand 2.1.1",
"futures-io",
]
[[package]]
name = "plain_path"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f1b940aa8e0562ece01eb12a9731bb8f6f0325c2c97c8629f852504f01d4537"
dependencies = [
"dirs 3.0.2",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
@@ -487,6 +756,17 @@ dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
@@ -526,24 +806,51 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.198"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.198"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook"
version = "0.3.17"
@@ -599,6 +906,18 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "ssh_cfg"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33130e7e8a61e5822f5f6f7f7127e4adad805fcc1a55dc83d2b5c15c1279014c"
dependencies = [
"async-fs",
"dirs 4.0.0",
"indexmap 1.9.3",
"plain_path",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
@@ -644,8 +963,35 @@ version = "0.1.0"
dependencies = [
"crossterm",
"futures",
"indexmap 2.6.0",
"lazy_static",
"ratatui",
"serde",
"serde_json",
"shlex",
"ssh_cfg",
"tokio",
"toml",
]
[[package]]
name = "thiserror"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -698,6 +1044,40 @@ dependencies = [
"syn",
]
[[package]]
name = "toml"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
"indexmap 2.6.0",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
@@ -733,6 +1113,12 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "waker-fn"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@@ -834,6 +1220,15 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
dependencies = [
"memchr",
]
[[package]]
name = "zerocopy"
version = "0.7.35"

View File

@@ -8,5 +8,12 @@ edition = "2021"
[dependencies]
crossterm = { version = "0.28.1", features = ["event-stream"] }
futures = "0.3.30"
indexmap = "2.6.0"
lazy_static = "1.5.0"
ratatui = { version = "0.28.1", features = ["all-widgets"] }
serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.128"
shlex = "1.3.0"
ssh_cfg = "0.3.0"
tokio = { version = "1.40.0", features = ["full"] }
toml = "0.8.19"

1
rustfmt.toml Normal file
View File

@@ -0,0 +1 @@
imports_granularity = "Crate"

View File

@@ -1,22 +1,28 @@
use std::error;
use crate::component::Component;
/// Application result type.
pub type AppResult<T> = std::result::Result<T, Box<dyn error::Error>>;
#[derive(Debug)]
pub enum Mode {
Normal,
Insert,
}
/// Application.
#[derive(Debug)]
pub struct App {
/// Is the application running?
pub running: bool,
/// counter
pub counter: u8,
pub mode: Mode,
}
impl Default for App {
fn default() -> Self {
Self {
running: true,
counter: 0,
mode: Mode::Normal,
}
}
}
@@ -34,16 +40,26 @@ impl App {
pub fn quit(&mut self) {
self.running = false;
}
}
pub fn increment_counter(&mut self) {
if let Some(res) = self.counter.checked_add(1) {
self.counter = res;
pub struct AppComponents {
pub components: Vec<Box<dyn Component>>,
}
impl Default for AppComponents {
fn default() -> Self {
Self {
components: Vec::new(),
}
}
}
pub fn decrement_counter(&mut self) {
if let Some(res) = self.counter.checked_sub(1) {
self.counter = res;
}
impl AppComponents {
pub fn new() -> Self {
Self::default()
}
pub fn add_component(&mut self, component: Box<dyn Component>) {
self.components.push(component);
}
}

13
src/component.rs Normal file
View File

@@ -0,0 +1,13 @@
use crossterm::event::{Event, KeyEvent};
use ratatui::{layout::Rect, Frame};
use crate::app::{App, AppComponents};
pub mod home;
pub mod input;
pub trait Component {
fn draw(&mut self, frame: &mut Frame, app: &mut App, area: Rect);
fn handle_event(&mut self, app: &mut App, event: &Event);
fn get_help(&self) -> Option<Vec<(&'static str, &'static str)>>;
}

76
src/component/home.rs Normal file
View File

@@ -0,0 +1,76 @@
use crate::{
app::App,
component::{input::Input, Component},
};
use crossterm::event::Event;
use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style},
widgets::{Block, BorderType, Borders, List, ListItem, Paragraph},
Frame,
};
pub struct Home {
input: Input,
}
impl Home {
pub fn new() -> Self {
Self {
input: Input::new(),
}
}
}
impl Component for Home {
fn draw(&mut self, frame: &mut Frame, app: &mut App, area: Rect) {
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3),
Constraint::Min(3),
Constraint::Length(3),
])
.split(frame.area());
let input_block = Block::default()
.title("Home")
.borders(Borders::ALL)
.border_type(BorderType::Rounded);
frame.render_widget(input_block, layout[0]);
let input_area = Rect::new(layout[0].x + 1, layout[0].y + 1, layout[0].width - 2, 1);
self.input.draw(frame, app, input_area);
let list_block = Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded);
frame.render_widget(list_block, layout[1]);
let list_area = Rect::new(
layout[1].x + 1,
layout[1].y + 1,
layout[1].width - 2,
layout[1].height - 2,
);
let items = vec![
ListItem::new("Item 1"),
ListItem::new("Item 2"),
ListItem::new("Item 3"),
];
let list = List::default().items(items);
frame.render_widget(list, list_area);
let help_block = Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded);
frame.render_widget(help_block, layout[2]);
let help_area = Rect::new(layout[2].x + 1, layout[2].y + 1, layout[2].width - 2, 1);
let help = Paragraph::new("Press 'q' to quit");
frame.render_widget(help, help_area);
}
fn handle_event(&mut self, app: &mut App, event: &Event) {
self.input.handle_event(app, event);
}
fn get_help(&self) -> Option<Vec<(&'static str, &'static str)>> {
self.input.get_help()
}
}

55
src/component/input.rs Normal file
View File

@@ -0,0 +1,55 @@
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use ratatui::{
layout::Rect,
widgets::{Block, Borders, Paragraph},
Frame,
};
use crate::app::App;
use super::Component;
#[derive(Debug)]
pub struct Input {
value: String,
}
impl Input {
pub fn new() -> Self {
Self {
value: String::new(),
}
}
}
impl Component for Input {
fn draw(&mut self, frame: &mut Frame, _app: &mut App, area: Rect) {
let input = Paragraph::new(self.value.clone());
frame.render_widget(input, area);
}
fn handle_event(&mut self, _app: &mut App, event: &Event) {
if let Event::Key(KeyEvent {
code,
kind: KeyEventKind::Press,
modifiers,
..
}) = event
{
use KeyCode::*;
match (code, modifiers) {
(Char(c), &KeyModifiers::NONE | &KeyModifiers::SHIFT) => {
self.value.push(c.to_owned());
}
(Backspace, &KeyModifiers::NONE) => {
self.value.pop();
}
_ => {}
}
}
}
fn get_help(&self) -> Option<Vec<(&'static str, &'static str)>> {
None
}
}

8
src/connection.rs Normal file
View File

@@ -0,0 +1,8 @@
use std::error::Error;
pub mod authentication;
pub mod connection_config;
pub trait Connection {
fn connect(&self) -> Result<(), Box<dyn Error>>;
}

View File

@@ -0,0 +1,33 @@
pub enum AuthenticationMethod {
Password(String),
PrivateKey(String),
}
pub struct Authentication {
pub auth_name: String,
pub method: AuthenticationMethod,
}
pub struct AuthenticationManager {
pub authentications: Vec<Authentication>,
}
impl AuthenticationManager {
pub fn new() -> Self {
Self {
authentications: Vec::new(),
}
}
pub fn add_authentication(&mut self, auth_name: String, method: AuthenticationMethod) {
self.authentications.push(Authentication {
auth_name,
method,
});
}
pub fn get_authentication(&self, auth_name: &str) -> Option<&Authentication> {
self.authentications.iter().find(|auth| auth.auth_name == auth_name)
}
}

View File

@@ -0,0 +1,77 @@
use std::{
fs::{File, OpenOptions},
io::Write,
os::unix::fs::OpenOptionsExt,
path::Path,
process::Command,
};
use serde::{Deserialize, Serialize};
use crate::{connection::authentication::{self, AuthenticationMethod}, global::AUTH_MAN};
use super::{authentication::Authentication, Connection};
#[derive(Debug, Serialize, Deserialize)]
pub enum ConnectionType {
SSH(SshConnectionConfig),
SH(ShConnectionConfig),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SshConnectionConfig {
pub host: String,
pub port: u16,
pub user: String,
pub auth_name: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ShConnectionConfig {
pub command: String,
pub path: String,
pub args: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ConnectionManager {
pub connection_type: ConnectionType,
}
impl Connection for ConnectionManager {
fn connect(&self) -> Result<(), Box<dyn std::error::Error>> {
let mut command = match &self.connection_type {
ConnectionType::SH(sh) => Command::new(&sh.command),
ConnectionType::SSH(ssh) => {
let authentication = AUTH_MAN.get_authentication(&ssh.auth_name);
match authentication {
Some(auth) => {
match &auth.method {
AuthenticationMethod::Password(password) => {
Command::new(format!("sshpass -p {} ssh", password))
}
AuthenticationMethod::PrivateKey(key) => {
let secret_path = Path::new("/tmp/tethers_tmp.tmp");
let mut secret_file = OpenOptions::new()
.write(true)
.create(true)
.mode(0o600)
.open(secret_path)?;
secret_file.set_len(0)?;
secret_file.write_all(key.as_bytes())?;
let mut command = Command::new("ssh");
command.args(["-i", secret_path.to_str().unwrap(), "-P", &ssh.port.to_string(), "-l", &ssh.user, &ssh.host]);
command
}
}
}
None => return Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, "Authentication not found"))),
}
}
};
println!("{:?}", command);
command.spawn()?.wait()?;
println!("exit!");
Ok(())
}
}

40
src/global.rs Normal file
View File

@@ -0,0 +1,40 @@
use futures::lock::Mutex;
use lazy_static::lazy_static;
use crate::connection::authentication::{AuthenticationManager, AuthenticationMethod};
lazy_static! {
pub static ref AUTH_MAN: AuthenticationManager = {
let mut auth_man = AuthenticationManager::new();
auth_man.add_authentication("tencent".to_string(), AuthenticationMethod::PrivateKey(
r#"-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwLsc4WtfQ9myeqYrsXhyJxg6pqsjE7NNgva9OO6bMRLUJOip
9fEMGk/cLXDFEqTiB5WmXCfHc+pOOomyA2OlMFW4hMfV7TB+c2BIEL7BHb+B+m50
ps8b1tFze9hqArPL/wZ6xDDks83v3MUSMBoq+rf5cOix/wJtm9dUtVODBYjlYaT9
wFDkuIjpxmLR6+Jiq+nJWVeBBIHABDjhj+qI2Qp4tRoQl1hzfcbFLzDMqI/HANEW
cYOJ+Z+TizA6TH3s8FBxU9NKQhA8Dur4jH+HG9XrsVGh9gckcI8iYWWEr1O6MvrB
MVtSEiSarsWpc732YxHDWyJ9BYaZq5BKMNNa5QIDAQABAoIBAA0Roxe2DDOxo2Sl
t/jEzhjQj8mVeT9wvSTQTmx54ZQmTrRiAoRm4Ac4uxlpuDNXcuxIvYsqN2Ao1z0o
8SVBsIXiiiyW0G7RBwIT2U8pYDcAMXQl4UseaCjc8xPGzQl5369fZRvApQF8SXOS
+XTBkYCG0SdS4B8GryAOLfRNwWiyjXkfkJiLdMjLaomOZMKLrq3mAjNPFoKX4piL
xBFztBhQwvNKp0GVNiz8WyEQ+nWTpl3l1cU1PE98Mk4YkCRBvwSiJlTJHdqWmnPU
ne3OeazNqucKWU+iWiTWb8Y4x0L/rZUf/N6KmQp5dhc3BzrOrjTXyytjm5zNxcTT
zsgf5tECgYEA9BvihyQ8+FY8bRqBqXXA95vecleqxeLfzL4PG19WZCoG3uTC5YuK
uPOV4awcJESP3JTOKBM9KYiaYKigFSES6GQIYD28tLz6b3DAj2GcCiTZbwLkVj33
A58EYfrKcbzTHN3P4Gzh8mV3067uk2CPan6SPWfP36BdHukIKnKrytcCgYEAyh6H
JUOXphcrWDVjca6PB23JbuUHLrT3I2hcM5IPj5lOEdx15efUp7+708Lm1LcPlnVy
eZlSCnPBlOnFeXULbxjFtPp4IaJSki470lTNZbvOMF7b4Os8f1axbWkby4dqsN9U
szp7Pb2VAt7TQjXdnAy0+w1uUrbh5nK27CHF7KMCgYEAq+xbvcxi+JBb3IeXnI8n
ifyITcW6q8Ze0udk4mViBgencSJytZJy8FH2VMuPwmdbXwKas5ThNbhU6hC5Nkhl
9bBGSeoRVsXmJ8ikhhc7+9T6InpJ8QeRSkaboposLLDNiv4Z4zksZTjbjQYeV0Ph
niJowsCzog9iplt/ec3rel8CgYA4nFxV/5yWFJ87UZjM2ouNFR76RIPCfUVLUYuh
liNtoQ7QyeLrxgc2BOni4hAtPCjNh4/MpzjnXIy33NJGtpXBRDr7+bt0EDb4YqK2
wrHU8MlLmwYS9VA4ilufvXTfiMpFSAg9OwARxuvZpfed7+BmONjoCFcCK98R4MeD
U+JP6QKBgBTBhg1ptSvWNhePXsdxiZOjOuo3/W/qLJmcCSIvk9c3rd877jtvxOC2
919rt4YO0gr2EYQWQ4cmCQvzaCEKX4st5ArA0XvcOTdlXMYQSaXoj96DwfpGspYu
1AG7ygBL7Aoyv5hB0g+7pgtbfzr6GRkb2t8x/uveZ8fu4C0txLAE
-----END RSA PRIVATE KEY-----"#.to_string()
));
auth_man
};
}

View File

@@ -1,28 +0,0 @@
use crate::app::{App, AppResult};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
/// Handles the key events and updates the state of [`App`].
pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
match key_event.code {
// Exit application on `ESC` or `q`
KeyCode::Esc | KeyCode::Char('q') => {
app.quit();
}
// Exit application on `Ctrl-C`
KeyCode::Char('c') | KeyCode::Char('C') => {
if key_event.modifiers == KeyModifiers::CONTROL {
app.quit();
}
}
// Counter handlers
KeyCode::Right => {
app.increment_counter();
}
KeyCode::Left => {
app.decrement_counter();
}
// Other handlers you could add here.
_ => {}
}
Ok(())
}

View File

@@ -1,46 +1,71 @@
use std::io;
use app::AppComponents;
use component::{home::Home, input::Input};
use connection::{
authentication::AuthenticationMethod, connection_config::{
ConnectionManager, ConnectionType, ShConnectionConfig, SshConnectionConfig,
}, Connection
};
use global::AUTH_MAN;
use ratatui::{backend::CrosstermBackend, Terminal};
use crate::{
app::{App, AppResult},
event::{Event, EventHandler},
handler::handle_key_events,
event::EventHandler,
tui::Tui,
util::{sh_config, ssh_config},
};
pub mod app;
pub mod component;
pub mod connection;
pub mod event;
pub mod handler;
pub mod tui;
pub mod ui;
pub mod util;
pub mod global;
#[tokio::main]
async fn main() -> AppResult<()> {
// Create an application.
let mut app = App::new();
// #[tokio::main]
// async fn main() -> AppResult<()> {
// // Create an application.
// let mut app = App::new();
// let mut components = AppComponents::new();
// components.add_component(Box::new(Home::new()));
// Initialize the terminal user interface.
let backend = CrosstermBackend::new(io::stdout());
let terminal = Terminal::new(backend)?;
let events = EventHandler::new(250);
let mut tui = Tui::new(terminal, events);
tui.init()?;
// // Initialize the terminal user interface.
// let backend = CrosstermBackend::new(io::stdout());
// let terminal = Terminal::new(backend)?; let events = EventHandler::new(250);
// let mut tui = Tui::new(terminal, events);
// tui.init()?;
// Start the main loop.
while app.running {
// Render the user interface.
tui.draw(&mut app)?;
// Handle events.
match tui.events.next().await? {
Event::Tick => app.tick(),
Event::Key(key_event) => handle_key_events(key_event, &mut app)?,
Event::Mouse(_) => {}
Event::Resize(_, _) => {}
}
}
// Exit the user interface.
tui.exit()?;
Ok(())
// // Start the main loop.
// while app.running {
// // Render the user interface.
// tui.draw(&mut app, &mut components)?;
// // Handle events.
// tui.handle_events(&mut app, &mut components).await?;
// }
// // Exit the user interface.
// tui.exit()?;
// Ok(())
// }
fn main() {
let config = ConnectionManager {
connection_type: ConnectionType::SSH(SshConnectionConfig {
host: "myhost.fallen-angle.com".to_string(),
port: 22,
user: "rainbus".to_string(),
auth_name: "tencent".to_string(),
}),
// connection_type: ConnectionType::SH(ShConnectionConfig {
// command: "bash".to_string(),
// path: "".to_string(),
// args: vec![],
// })
};
config.connect().unwrap();
}

View File

@@ -1,12 +1,15 @@
use crate::app::{App, AppResult};
use crate::event::EventHandler;
use crate::ui;
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen};
use ratatui::backend::Backend;
use ratatui::Terminal;
use std::io;
use std::panic;
use crate::{
app::{self, App, AppComponents, AppResult},
component,
event::{Event, EventHandler},
ui,
};
use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture, KeyCode},
terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{backend::Backend, Terminal};
use std::{io, panic};
/// Representation of a terminal user interface.
///
@@ -50,11 +53,33 @@ impl<B: Backend> Tui<B> {
///
/// [`Draw`]: ratatui::Terminal::draw
/// [`rendering`]: crate::ui::render
pub fn draw(&mut self, app: &mut App) -> AppResult<()> {
self.terminal.draw(|frame| ui::render(app, frame))?;
pub fn draw(&mut self, app: &mut App, components: &mut AppComponents) -> AppResult<()> {
self.terminal
.draw(|frame| ui::render(app, components, frame))?;
Ok(())
}
pub async fn handle_events(
&mut self,
app: &mut App,
components: &mut AppComponents,
) -> AppResult<()> {
match self.events.next().await? {
Event::Key(key_event) => {
for component in components.components.iter_mut() {
component.handle_event(app, &crossterm::event::Event::Key(key_event));
}
if key_event.code == KeyCode::Esc {
app.quit();
}
Ok(())
}
Event::Tick => Ok(()),
Event::Mouse(_) => Ok(()),
Event::Resize(_, _) => Ok(()),
}
}
/// Resets the terminal interface.
///
/// This function is also used for the panic hook to revert

View File

@@ -1,34 +1,31 @@
use ratatui::{
layout::Alignment,
layout::{Alignment, Constraint, Direction, Layout},
style::{Color, Style},
widgets::{Block, BorderType, Paragraph},
widgets::{Block, BorderType, Borders, Paragraph},
Frame,
};
use crate::app::App;
use crate::{
app::{App, AppComponents},
component::input,
};
/// Renders the user interface widgets.
pub fn render(app: &mut App, frame: &mut Frame) {
pub fn render(app: &mut App, components: &mut AppComponents, frame: &mut Frame) {
// This is where you add new widgets.
// See the following resources:
// - https://docs.rs/ratatui/latest/ratatui/widgets/index.html
// - https://github.com/ratatui/ratatui/tree/master/examples
frame.render_widget(
Paragraph::new(format!(
"This is a tui template.\n\
Press `Esc`, `Ctrl-C` or `q` to stop running.\n\
Press left and right to increment and decrement the counter respectively.\n\
Counter: {}",
app.counter
))
.block(
Block::bordered()
.title("Template")
.title_alignment(Alignment::Center)
.border_type(BorderType::Rounded),
)
.style(Style::default().fg(Color::Cyan).bg(Color::Black))
.centered(),
frame.area(),
)
// let layout = Layout::default()
// .direction(Direction::Vertical)
// .constraints([
// Constraint::Length(3),
// Constraint::Min(1),
// Constraint::Length(1),
// ])
// .split(frame.area());
// for (index, component) in components.components.iter_mut().enumerate() {
// component.draw(frame, app, layout[index]);
// }
components.components[0].draw(frame, app, frame.area());
}

2
src/util.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod sh_config;
pub mod ssh_config;

22
src/util/sh_config.rs Normal file
View File

@@ -0,0 +1,22 @@
use std::{error::Error, path::Path};
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub fn get_shells() -> Result<Vec<String>, Box<dyn Error>> {
let path = Path::new("/etc/shells");
let file_content = std::fs::read_to_string(path)?;
let shells = file_content
.lines()
.filter(|line| !(line.starts_with("#") || line.is_empty()))
.map(|line| line.to_string())
.collect::<Vec<String>>();
Ok(shells)
}
#[cfg(target_os = "windows")]
pub fn get_shells() -> Result<Vec<String>, Box<dyn Error>> {
let shells = vec![
"C:\\Windows\\System32\\cmd.exe",
"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
];
Ok(shells)
}

9
src/util/ssh_config.rs Normal file
View File

@@ -0,0 +1,9 @@
use std::error::Error;
use ssh_cfg::{SshConfig, SshConfigParser};
pub async fn ssh_config() -> Result<SshConfig, Box<dyn Error>> {
let sshs = SshConfigParser::parse_home().await?;
Ok(sshs)
}