basic gui and login to imap
This commit is contained in:
commit
78f5c4655c
11 changed files with 2449 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
/target
|
||||
docker-data/
|
||||
config.toml
|
||||
.idea/
|
||||
70
CLAUDE.md
Normal file
70
CLAUDE.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a Rust terminal user interface (TUI) application built with Ratatui.
|
||||
It will evolve to become a tui mail client
|
||||
|
||||
## Build and Run Commands
|
||||
|
||||
```bash
|
||||
# Build the project
|
||||
cargo build
|
||||
|
||||
# Run the application
|
||||
cargo run
|
||||
|
||||
# Build optimized release version
|
||||
cargo build --release
|
||||
|
||||
# Check code without building
|
||||
cargo check
|
||||
|
||||
# Format code
|
||||
cargo fmt
|
||||
|
||||
# Run clippy linter
|
||||
cargo clippy
|
||||
```
|
||||
|
||||
## Test Mail Server
|
||||
|
||||
A Docker-based IMAP mail server is available for testing:
|
||||
|
||||
```bash
|
||||
# Start the mail server
|
||||
docker-compose up -d
|
||||
|
||||
# Create a test user
|
||||
docker exec -it mailserver setup email add test@example.com password123
|
||||
|
||||
# Stop the mail server
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
Connection details: localhost:143 (IMAP) or localhost:993 (IMAPS). See `MAIL_SERVER_SETUP.md` for detailed usage.
|
||||
|
||||
## Architecture
|
||||
|
||||
This is a single-file application (`src/main.rs`) following the standard terminal application lifecycle:
|
||||
|
||||
1. **Terminal Setup**: Enable raw mode and enter alternate screen
|
||||
2. **Event Loop**:
|
||||
- Render UI using Ratatui's declarative widget system
|
||||
- Poll for keyboard events (200ms timeout)
|
||||
- Exit on 'q' or Escape key
|
||||
3. **Cleanup**: Disable raw mode, leave alternate screen, restore cursor
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
- **ratatui (0.29)**: TUI framework providing widgets, layouts, and rendering
|
||||
- **crossterm (0.28)**: Cross-platform terminal manipulation (raw mode, events, alternate screen)
|
||||
|
||||
## Development Notes
|
||||
|
||||
- Uses Rust edition 2024
|
||||
- The application uses a constraint-based layout system to center content
|
||||
- Terminal is set to raw mode to capture individual key presses
|
||||
- The alternate screen prevents terminal history pollution
|
||||
2124
Cargo.lock
generated
Normal file
2124
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "hello-ratatui"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
ratatui = "0.30"
|
||||
crossterm = "0.29"
|
||||
imap = "2.4"
|
||||
native-tls = "0.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "1.0"
|
||||
78
MAIL_SERVER_SETUP.md
Normal file
78
MAIL_SERVER_SETUP.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# Mail Server Setup for Testing
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Start the mail server:**
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
2. **Create a test user:**
|
||||
```bash
|
||||
docker exec -it mailserver setup email add test@example.com password123
|
||||
```
|
||||
|
||||
3. **Verify the server is running:**
|
||||
```bash
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
## IMAP Connection Details
|
||||
|
||||
- **Host:** localhost
|
||||
- **IMAP Port:** 143 (unencrypted) or 993 (SSL/TLS)
|
||||
- **Username:** test@example.com
|
||||
- **Password:** password123
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```bash
|
||||
# Stop the mail server
|
||||
docker-compose down
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f mailserver
|
||||
|
||||
# List all email accounts
|
||||
docker exec -it mailserver setup email list
|
||||
|
||||
# Add another user
|
||||
docker exec -it mailserver setup email add user2@example.com pass456
|
||||
|
||||
# Delete a user
|
||||
docker exec -it mailserver setup email del test@example.com
|
||||
|
||||
# Access the container shell
|
||||
docker exec -it mailserver bash
|
||||
```
|
||||
|
||||
## Testing with telnet
|
||||
|
||||
You can test IMAP connectivity:
|
||||
```bash
|
||||
telnet localhost 143
|
||||
```
|
||||
|
||||
Then try IMAP commands:
|
||||
```
|
||||
a1 LOGIN test@example.com password123
|
||||
a2 LIST "" "*"
|
||||
a3 SELECT INBOX
|
||||
a4 LOGOUT
|
||||
```
|
||||
|
||||
## Send Test Email
|
||||
|
||||
```bash
|
||||
# From within the container
|
||||
docker exec -it mailserver bash
|
||||
echo "Test email body" | mail -s "Test Subject" test@example.com
|
||||
```
|
||||
|
||||
Or use SMTP (port 25/587) from your application.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Check logs: `docker-compose logs mailserver`
|
||||
- Ensure ports aren't already in use
|
||||
- Data persists in `./docker-data/` directory
|
||||
6
config.toml
Normal file
6
config.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[imap]
|
||||
host = "localhost"
|
||||
port = 143
|
||||
username = "sander@sanderhautvast.net"
|
||||
password = "boompje"
|
||||
use_tls = false
|
||||
6
config.toml.example
Normal file
6
config.toml.example
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[imap]
|
||||
host = "localhost"
|
||||
port = 143
|
||||
username = "test@example.com"
|
||||
password = "password123"
|
||||
use_tls = false
|
||||
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
mailserver:
|
||||
image: mailserver/docker-mailserver:latest
|
||||
container_name: mailserver
|
||||
hostname: mail.example.com
|
||||
ports:
|
||||
- "25:25" # SMTP
|
||||
- "143:143" # IMAP
|
||||
- "587:587" # SMTP Submission
|
||||
- "993:993" # IMAPS
|
||||
volumes:
|
||||
- ./docker-data/mail-data:/var/mail
|
||||
- ./docker-data/mail-state:/var/mail-state
|
||||
- ./docker-data/mail-logs:/var/log/mail
|
||||
- ./docker-data/config:/tmp/docker-mailserver
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
environment:
|
||||
- ENABLE_SPAMASSASSIN=0
|
||||
- ENABLE_CLAMAV=0
|
||||
- ENABLE_FAIL2BAN=0
|
||||
- ENABLE_POSTGREY=0
|
||||
- ONE_DIR=1
|
||||
- DMS_DEBUG=0
|
||||
- PERMIT_DOCKER=network
|
||||
- SSL_TYPE=
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
restart: unless-stopped
|
||||
24
src/config.rs
Normal file
24
src/config.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use serde::Deserialize;
|
||||
use std::fs;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Config {
|
||||
pub imap: ImapConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct ImapConfig {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub use_tls: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let content = fs::read_to_string("config.toml")?;
|
||||
let config: Config = toml::from_str(&content)?;
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
1
src/lib.rs
Normal file
1
src/lib.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod config;
|
||||
94
src/main.rs
Normal file
94
src/main.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
use std::{io, net::TcpStream, time::Duration};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
layout::{Alignment, Constraint, Direction, Layout},
|
||||
style::{Color, Style},
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
Terminal,
|
||||
};
|
||||
use hello_ratatui::config::Config;
|
||||
|
||||
fn imap_login(config: &Config) -> Result<String, String> {
|
||||
let imap = &config.imap;
|
||||
let stream = TcpStream::connect((&*imap.host, imap.port)).map_err(|e| e.to_string())?;
|
||||
let client = imap::Client::new(stream);
|
||||
let mut session = client
|
||||
.login(&imap.username, &imap.password)
|
||||
.map_err(|(e, _)| e.to_string())?;
|
||||
let _ = session.logout();
|
||||
Ok(format!("Logged in as {}", imap.username))
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let config = Config::load().unwrap();
|
||||
let imap_status = match imap_login(&config) {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => format!("IMAP error: {}", e),
|
||||
};
|
||||
|
||||
// --- Setup terminal ---
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen)?;
|
||||
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
// --- Main loop ---
|
||||
loop {
|
||||
terminal.draw(|frame| {
|
||||
// Split the screen into a centered area
|
||||
let area = frame.area();
|
||||
let vertical = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Percentage(40),
|
||||
Constraint::Percentage(20),
|
||||
Constraint::Percentage(40),
|
||||
])
|
||||
.split(area);
|
||||
|
||||
let horizontal = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([
|
||||
Constraint::Percentage(25),
|
||||
Constraint::Percentage(50),
|
||||
Constraint::Percentage(25),
|
||||
])
|
||||
.split(vertical[1]);
|
||||
|
||||
let center = horizontal[1];
|
||||
|
||||
// Render a bordered box with "Hello, World!"
|
||||
let paragraph = Paragraph::new(imap_status.as_str())
|
||||
.block(Block::default().title("Ratatui").borders(Borders::ALL))
|
||||
.style(Style::default().fg(Color::Green))
|
||||
.alignment(Alignment::Center);
|
||||
|
||||
frame.render_widget(paragraph, center);
|
||||
})?;
|
||||
|
||||
// --- Input handling: quit on 'q' or Escape ---
|
||||
if event::poll(Duration::from_millis(200))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Restore terminal ---
|
||||
disable_raw_mode()?;
|
||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue