Skip to content

Commit 2881af8

Browse files
committed
wip: continue client login impl
1 parent 4677c8d commit 2881af8

File tree

20 files changed

+156
-45
lines changed

20 files changed

+156
-45
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ colored = "3.0.0"
4545
cron = { version = "0.15.0", features = ["serde"] }
4646
futures = "0.3.30"
4747
futures-util = "0.3.31"
48+
headers = "0.4.1"
4849
image = "0.25.5"
4950
native_db = { git = "https://github.com/vincent-herlemont/native_db", features = [
5051
"tokio",

sandpolis-client/src/tui/loading.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@ use ratatui::{
44
prelude::*,
55
style::{Style, Stylize},
66
text::Line,
7-
widgets::{Block, Borders, Paragraph, WidgetRef},
7+
widgets::{Block, Borders, Paragraph, Widget, WidgetRef},
88
};
9+
use std::time::{Duration, Instant};
910
use tui_popup::SizedWidgetRef;
1011

1112
#[derive(Debug, Clone)]
1213
pub struct LoadingWidget {
1314
message: String,
1415
spinner_chars: Vec<char>,
1516
current_frame: usize,
17+
last_update: Instant,
18+
frame_duration: Duration,
1619
}
1720

1821
impl LoadingWidget {
@@ -21,15 +24,29 @@ impl LoadingWidget {
2124
message: message.to_string(),
2225
spinner_chars: vec!['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
2326
current_frame: 0,
27+
last_update: Instant::now(),
28+
frame_duration: Duration::from_millis(100), // 10 FPS animation
2429
}
2530
}
2631

2732
pub fn next_frame(&mut self) {
2833
self.current_frame = (self.current_frame + 1) % self.spinner_chars.len();
34+
self.last_update = Instant::now();
35+
}
36+
37+
pub fn update_animation(&mut self) {
38+
let now = Instant::now();
39+
if now.duration_since(self.last_update) >= self.frame_duration {
40+
self.next_frame();
41+
}
2942
}
3043

3144
fn get_spinner_char(&self) -> char {
32-
self.spinner_chars[self.current_frame]
45+
// Calculate current frame based on elapsed time for automatic animation
46+
let elapsed = Instant::now().duration_since(self.last_update);
47+
let additional_frames = (elapsed.as_millis() / self.frame_duration.as_millis()) as usize;
48+
let current_frame = (self.current_frame + additional_frames) % self.spinner_chars.len();
49+
self.spinner_chars[current_frame]
3350
}
3451
}
3552

sandpolis-core/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ anyhow = { workspace = true }
1010
bevy = { workspace = true, optional = true }
1111
clap = { workspace = true, optional = true }
1212
colored = { workspace = true, optional = true }
13+
headers = { workspace = true, optional = true }
1314
native_db = { workspace = true }
1415
native_model = { workspace = true }
1516
regex = { workspace = true }
@@ -20,7 +21,7 @@ validator = { workspace = true }
2021

2122
[features]
2223
default = ["dep:clap", "dep:colored"]
23-
server = []
24+
server = ["dep:headers"]
2425
bootagent = []
2526
agent = []
2627
client = []

sandpolis-core/src/lib.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use anyhow::Result;
2+
#[cfg(feature = "server")]
3+
use headers::{Header, HeaderName, HeaderValue};
24
use native_db::ToKey;
35
use regex::Regex;
46
use serde::{Deserialize, Serialize, de::DeserializeOwned};
@@ -421,6 +423,36 @@ impl ToKey for RealmName {
421423
}
422424
}
423425

426+
#[cfg(feature = "server")]
427+
impl Header for RealmName {
428+
fn name() -> &'static HeaderName {
429+
static NAME: HeaderName = HeaderName::from_static("x-realm");
430+
&NAME
431+
}
432+
433+
fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
434+
where
435+
I: Iterator<Item = &'i HeaderValue>,
436+
{
437+
Ok(values
438+
.next()
439+
.ok_or_else(headers::Error::invalid)?
440+
.to_str()
441+
.map_err(|_| headers::Error::invalid())?
442+
.parse()
443+
.map_err(|_| headers::Error::invalid())?)
444+
}
445+
446+
fn encode<E>(&self, values: &mut E)
447+
where
448+
E: Extend<HeaderValue>,
449+
{
450+
values.extend(std::iter::once(
451+
HeaderValue::from_str(&self.to_string()).expect("Realm names only allow ascii 32-127"),
452+
));
453+
}
454+
}
455+
424456
#[cfg(test)]
425457
mod test_realm_name {
426458
use super::*;

sandpolis-network/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use anyhow::Context;
55
use anyhow::Result;
66
use anyhow::anyhow;
77
use anyhow::bail;
8+
use axum::http::HeaderValue;
89
use chrono::{DateTime, Utc};
910
use config::NetworkLayerConfig;
1011
use cron::Schedule;
@@ -15,6 +16,7 @@ use native_db::ToKey;
1516
use native_model::Model;
1617
use reqwest::ClientBuilder;
1718
use reqwest::Method;
19+
use reqwest::header::CONTENT_TYPE;
1820
use reqwest_websocket::RequestBuilderExt;
1921
use sandpolis_core::ClusterId;
2022
use sandpolis_core::{InstanceId, RealmName};
@@ -174,9 +176,9 @@ impl NetworkLayer {
174176

175177
},
176178
Err(e) => {
177-
debug!(error = %e, "Poll request failed");
178179
// Wait before retrying
179180
let timeout = {connection.retry.write().unwrap().next().unwrap()};
181+
debug!(error = %e, waiting = ?timeout, "Poll request failed");
180182
sleep(timeout).await;
181183
},
182184
}
@@ -284,6 +286,8 @@ impl OutboundConnection {
284286
method,
285287
format!("https://{}.{}/{endpoint}", self.cluster_id, self.realm),
286288
)
289+
.header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
290+
.header("x-realm", self.realm.to_string())
287291
.body(body)
288292
.send()
289293
.await?

sandpolis-realm/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ impl RealmClientCert {
428428

429429
#[cfg(feature = "client")]
430430
pub fn ca(&self) -> Result<reqwest::Certificate> {
431-
Ok(reqwest::Certificate::from_pem(&self.ca)?)
431+
Ok(reqwest::Certificate::from_der(&self.ca)?)
432432
}
433433

434434
#[cfg(feature = "client")]

sandpolis-realm/src/server.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use axum::{
1414
use axum_server::tls_rustls::RustlsConfig;
1515
use axum_server::{accept::Accept, tls_rustls::RustlsAcceptor};
1616
use futures_util::future::BoxFuture;
17-
use headers::{self, Header, HeaderName, HeaderValue};
1817
use rcgen::BasicConstraints;
1918
use rcgen::Certificate;
2019
use rcgen::CertificateParams;

sandpolis-server/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ tracing = { workspace = true }
2828
validator = { workspace = true }
2929

3030
[features]
31-
server = []
32-
client = []
33-
agent = []
31+
server = ["sandpolis-network/server"]
32+
client = ["sandpolis-network/client"]
33+
agent = ["sandpolis-network/agent"]
3434
client-tui = ["client"]
3535
client-gui = ["client"]

sandpolis-server/src/lib.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize};
1717
use serde_with::DeserializeFromStr;
1818
use std::sync::Arc;
1919
use std::time::Duration;
20-
use tracing::debug;
20+
use tracing::{debug, info};
2121
use validator::Validate;
2222

2323
#[cfg(feature = "client")]
@@ -59,13 +59,16 @@ impl ServerLayer {
5959
connections
6060
}
6161

62+
#[cfg(any(feature = "agent", feature = "client"))] // Temporary
6263
pub async fn connect(&self, url: ServerUrl) -> Result<ServerConnection> {
6364
let inner = self.network.connect_server(url)?;
6465

66+
debug!("Fetching server banner");
67+
6568
// Fetch banner before we return a complete connection
6669
let response: GetBannerResponse = inner
6770
.get(
68-
"/server/banner",
71+
"server/banner",
6972
GetBannerRequest {
7073
#[cfg(feature = "client-gui")]
7174
include_image: true,
@@ -85,7 +88,7 @@ impl ServerLayer {
8588
}
8689

8790
/// Contains information about the server useful for prospective logins
88-
#[derive(Serialize, Deserialize, Debug)]
91+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
8992
pub struct ServerBanner {
9093
/// Indicates that only admin users will be allowed to login
9194
pub maintenance: bool,
@@ -125,7 +128,17 @@ pub struct ServerConnection {
125128

126129
impl ServerConnection {
127130
pub async fn login(&self, request: LoginRequest) -> Result<LoginResponse> {
128-
Ok(self.inner.post("/login", request).await?)
131+
// TODO span username
132+
debug!(username = %request.username, "Attempting login");
133+
134+
let result = self.inner.post("user/login", request).await;
135+
match result {
136+
Ok(LoginResponse::Ok(_)) => {
137+
info!("Login succeeded");
138+
}
139+
_ => {}
140+
}
141+
result
129142
}
130143
}
131144

0 commit comments

Comments
 (0)