[Add] basic split
This commit is contained in:
parent
a0ff6b1082
commit
3cdf7c62d5
77 changed files with 506 additions and 648 deletions
17
iota-core/Cargo.toml
Normal file
17
iota-core/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "iota-core"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
iota-state = { path = "../iota-state" }
|
||||
ttp-core = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-core" }
|
||||
ttp-native = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-native" }
|
||||
|
||||
dashmap = "6.1.0"
|
||||
json = "*"
|
||||
once_cell = "1.21.3"
|
||||
pnet = "0.35.0"
|
||||
ratatui = "0.30.0"
|
||||
reqwest = "0.13.2"
|
||||
tokio = { version = "1.50.0", features = ["full"] }
|
||||
151
iota-core/src/main.rs
Normal file
151
iota-core/src/main.rs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
use pnet::datalink::NetworkInterface;
|
||||
use tokio::time::{Duration, sleep};
|
||||
|
||||
use iota_state::{ACTIVE_TASKS, APP_STATE, AppState, RELOAD, SHUTDOWN};
|
||||
|
||||
#[tokio::main(flavor = "multi_thread", worker_threads = 16)]
|
||||
#[allow(unused_must_use, dead_code)]
|
||||
async fn main() {
|
||||
while *RELOAD.read().await {
|
||||
*RELOAD.write().await = false;
|
||||
*SHUTDOWN.write().await = false;
|
||||
|
||||
let ui = start_tui();
|
||||
|
||||
let (eula, tos_pp) = consent_state::check(ui.clone()).await;
|
||||
|
||||
if !eula {
|
||||
*SHUTDOWN.write().await = true;
|
||||
loop {
|
||||
if ACTIVE_TASKS.is_empty() {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
println!("You need to accept our End User Licence Agreement before launching!");
|
||||
println!("You can find this at 'agreements'!");
|
||||
return;
|
||||
}
|
||||
if !tos_pp {
|
||||
*SHUTDOWN.write().await = true;
|
||||
loop {
|
||||
if ACTIVE_TASKS.is_empty() {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
println!(
|
||||
"Please accept our Privacy Policy & Terms of Serivce before using Tensamin Services!"
|
||||
);
|
||||
println!("In future releases this will be optional!");
|
||||
println!("You can find this at 'agreements'!");
|
||||
return;
|
||||
}
|
||||
iota_state::setup();
|
||||
|
||||
let main_screen = MainScreen::new(ui.clone()).await;
|
||||
ui.set_screen(Box::new(main_screen)).await;
|
||||
|
||||
// LANGUAGE PACK
|
||||
if let Err(e) = language_creator::create_languages() {
|
||||
println!("Language pack creation failed: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// UI
|
||||
logger::startup();
|
||||
|
||||
// BASIC CONFIGURATION
|
||||
&CONFIG.write().await.load();
|
||||
|
||||
// USER MANAGEMENT
|
||||
if let Err(_) = user_manager::load_users().await {
|
||||
log_t!("user_load_failed");
|
||||
}
|
||||
|
||||
let mut sb = "".to_string();
|
||||
|
||||
for up in user_manager::get_users() {
|
||||
sb = sb + "," + &up.user_id.to_string().as_str();
|
||||
}
|
||||
|
||||
if !sb.is_empty() {
|
||||
{}
|
||||
sb.remove(0);
|
||||
sb = sb + ",";
|
||||
}
|
||||
log!(
|
||||
"IOTA ID: {}",
|
||||
CONFIG.read().await.get_iota_id().to_string()
|
||||
);
|
||||
log!("User IDS: {}", sb);
|
||||
|
||||
// COMMUNITY MANAGEMENT
|
||||
/* registry::load_interactables().await;
|
||||
community_manager::load_communities().await;
|
||||
community_manager::save_communities().await;
|
||||
let mut sb1 = "".to_string();
|
||||
for cp in community_manager::get_communities().await {
|
||||
sb1 = sb1 + "," + &cp.get_name().to_string().as_str();
|
||||
}
|
||||
|
||||
if !sb1.is_empty() {
|
||||
sb1.remove(0);
|
||||
sb1 = sb1 + ",";
|
||||
}
|
||||
log!("Community IDS: {}", sb1); */
|
||||
let port = CONFIG.read().await.get_port();
|
||||
let mut ip = "0.0.0.0".to_string();
|
||||
for iface in pnet::datalink::interfaces() {
|
||||
let iface: NetworkInterface = iface;
|
||||
if iface.ips.len() > 0 {
|
||||
let ipsv = format!("{}", iface.ips[0]);
|
||||
let ips: &str = ipsv.split('/').next().unwrap_or("");
|
||||
if format!("{}", ips).starts_with("10.") || format!("{}", ips).starts_with("192.") {
|
||||
ip = ips.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
if start(port).await {
|
||||
log_t!("community_active", ip, port.to_string());
|
||||
} else {
|
||||
if port < 1024 {
|
||||
log_t!("community_start_error_admin", port.to_string());
|
||||
} else {
|
||||
log_t!("community_start_error", port.to_string());
|
||||
}
|
||||
} */
|
||||
if !has_dir("web") {
|
||||
download_and_extract_zip(
|
||||
"https://omega.tensamin.net/api/download/iota_frontend",
|
||||
"web",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let _ = omikron::omikron_connection::get_omikron_connection().await;
|
||||
|
||||
log_t!("setup_completed");
|
||||
loop {
|
||||
if *SHUTDOWN.read().await {
|
||||
break;
|
||||
}
|
||||
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
if *RELOAD.read().await {
|
||||
loop {
|
||||
if ACTIVE_TASKS.is_empty() {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
&CONFIG.write().await.clear();
|
||||
user_manager::clear();
|
||||
/*community_manager::clear();*/
|
||||
*APP_STATE.lock().unwrap() = AppState::new();
|
||||
}
|
||||
ui.terminal.lock().unwrap().clear();
|
||||
ui.terminal.lock().unwrap().flush();
|
||||
}
|
||||
}
|
||||
175
iota-core/src/terms/buttons.rs
Normal file
175
iota-core/src/terms/buttons.rs
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
use ratatui::{
|
||||
layout::{Alignment, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
};
|
||||
|
||||
use crate::terms::focus::Focus;
|
||||
|
||||
#[allow(mismatched_lifetime_syntaxes)]
|
||||
pub fn checkbox(label: &str, checked: bool, active: bool, allowed: bool) -> Line {
|
||||
let box_char = if checked { "[x]" } else { "[ ]" };
|
||||
let (box_style, text_style) = if active {
|
||||
if allowed {
|
||||
(
|
||||
Style::default()
|
||||
.fg(Color::Yellow)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
Style::default()
|
||||
.fg(Color::Yellow)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Style::default().fg(Color::Gray),
|
||||
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(Style::default(), Style::default())
|
||||
};
|
||||
Line::from(vec![
|
||||
Span::styled(box_char, box_style),
|
||||
Span::raw(" "),
|
||||
Span::styled(label, text_style),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn draw_button(f: &mut ratatui::Frame, area: Rect, label: &str, style: Style) {
|
||||
let p = Paragraph::new(Span::styled(label, style))
|
||||
.alignment(Alignment::Center)
|
||||
.block(Block::default().borders(Borders::ALL));
|
||||
f.render_widget(p, area);
|
||||
}
|
||||
pub fn draw_buttons(
|
||||
f: &mut ratatui::Frame,
|
||||
area: Rect,
|
||||
current_focus: Focus,
|
||||
state: (bool, bool),
|
||||
update_needed: bool,
|
||||
downgrade_scenario: bool,
|
||||
tos_or_privacy: bool,
|
||||
) {
|
||||
let cancel_text = if update_needed {
|
||||
"[Q] Quit"
|
||||
} else {
|
||||
"[Q] Not now"
|
||||
};
|
||||
let continue_text = if downgrade_scenario {
|
||||
"Downgrade"
|
||||
} else {
|
||||
"Continue"
|
||||
};
|
||||
let mut buttons = vec![
|
||||
(cancel_text, Focus::Cancel),
|
||||
(continue_text, Focus::Continue),
|
||||
];
|
||||
if tos_or_privacy {
|
||||
buttons.push(("Continue with Tensamin Services", Focus::ContinueAll));
|
||||
}
|
||||
|
||||
let padding = 2;
|
||||
let min_widths: Vec<u16> = buttons
|
||||
.iter()
|
||||
.map(|(label, _)| label.len() as u16 + padding)
|
||||
.collect();
|
||||
|
||||
let widths = compute_widths(area.width, &min_widths);
|
||||
|
||||
let mut x = area.x;
|
||||
|
||||
for ((label, focus), width) in buttons.iter().zip(widths) {
|
||||
let chunk = Rect {
|
||||
x,
|
||||
y: area.y,
|
||||
width,
|
||||
height: area.height,
|
||||
};
|
||||
x += width;
|
||||
|
||||
let is_focused = current_focus == *focus;
|
||||
|
||||
let style = match focus {
|
||||
Focus::Cancel => {
|
||||
if is_focused {
|
||||
Style::default()
|
||||
.fg(Color::Black)
|
||||
.bg(Color::Red)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(Color::Red)
|
||||
}
|
||||
}
|
||||
|
||||
Focus::Continue => {
|
||||
if is_focused && state.0 {
|
||||
Style::default()
|
||||
.fg(Color::Black)
|
||||
.bg(Color::Green)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else if state.0 {
|
||||
Style::default().fg(Color::Green)
|
||||
} else {
|
||||
Style::default().fg(Color::DarkGray)
|
||||
}
|
||||
}
|
||||
|
||||
Focus::ContinueAll => {
|
||||
if is_focused && state.1 {
|
||||
Style::default()
|
||||
.fg(Color::Black)
|
||||
.bg(Color::Green)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else if state.1 {
|
||||
Style::default().fg(Color::Green)
|
||||
} else {
|
||||
Style::default().fg(Color::DarkGray)
|
||||
}
|
||||
}
|
||||
|
||||
_ => Style::default().fg(Color::DarkGray),
|
||||
};
|
||||
|
||||
draw_button(f, chunk, label, style);
|
||||
}
|
||||
}
|
||||
pub fn compute_widths(area_width: u16, min_widths: &[u16]) -> Vec<u16> {
|
||||
let mut widths = vec![0; min_widths.len()];
|
||||
let mut remaining: Vec<usize> = (0..min_widths.len()).collect();
|
||||
|
||||
let mut remaining_width = area_width;
|
||||
|
||||
while !remaining.is_empty() {
|
||||
let count = remaining.len() as u16;
|
||||
let equal = remaining_width / count;
|
||||
|
||||
let mut clamped = Vec::new();
|
||||
|
||||
for &i in &remaining {
|
||||
if min_widths[i] > equal {
|
||||
widths[i] = min_widths[i];
|
||||
remaining_width -= min_widths[i];
|
||||
clamped.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
if clamped.is_empty() {
|
||||
let mut remainder = remaining_width % count;
|
||||
for &i in &remaining {
|
||||
widths[i] = equal
|
||||
+ if remainder > 0 {
|
||||
remainder -= 1;
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
remaining.retain(|i| !clamped.contains(i));
|
||||
}
|
||||
|
||||
widths
|
||||
}
|
||||
73
iota-core/src/terms/doc.rs
Normal file
73
iota-core/src/terms/doc.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
use json::{JsonValue, object::Object};
|
||||
|
||||
use crate::{terms::terms_getter::Type, util::file_util::load_file};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[allow(unused)]
|
||||
pub struct Doc {
|
||||
version: String,
|
||||
hash: String,
|
||||
pub doc_type: Type,
|
||||
timestamp: u64,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Doc {
|
||||
pub fn new(version: String, hash: String, doc_type: Type, timestamp: u64) -> Doc {
|
||||
Doc {
|
||||
version,
|
||||
hash,
|
||||
doc_type,
|
||||
timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn equals_some(&self, other: &Option<Self>) -> bool {
|
||||
if let Some(other) = other {
|
||||
self.get_version() == other.get_version() && self.get_hash() == other.get_hash()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
pub fn equals(&self, other: &Self) -> bool {
|
||||
self.get_version() == other.get_version() && self.get_hash() == other.get_hash()
|
||||
}
|
||||
|
||||
pub fn get_version(&self) -> String {
|
||||
self.version.clone()
|
||||
}
|
||||
pub fn get_hash(&self) -> String {
|
||||
self.hash.clone()
|
||||
}
|
||||
pub fn get_time(&self) -> u64 {
|
||||
self.timestamp.clone()
|
||||
}
|
||||
pub fn get_content(&self) -> String {
|
||||
load_file(
|
||||
format!("docs/{}/", self.doc_type.to_str()).as_str(),
|
||||
format!("{}.md", self.version).as_str(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> JsonValue {
|
||||
let mut json = JsonValue::new_object();
|
||||
|
||||
let _ = json.insert("version", self.version.clone());
|
||||
let _ = json.insert("hash", self.hash.clone());
|
||||
let _ = json.insert("unix", self.timestamp.clone());
|
||||
|
||||
json
|
||||
}
|
||||
pub fn from_json(doc_type: Type, json: Object) -> Option<Self> {
|
||||
let hash = json.get("hash")?.as_str()?.to_string();
|
||||
let version = json.get("version")?.as_str()?.to_string();
|
||||
let timestamp = json.get("unix")?.as_u64()?;
|
||||
|
||||
Some(Doc {
|
||||
version,
|
||||
hash,
|
||||
doc_type,
|
||||
timestamp,
|
||||
})
|
||||
}
|
||||
}
|
||||
25
iota-core/src/terms/focus.rs
Normal file
25
iota-core/src/terms/focus.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Focus {
|
||||
Eula,
|
||||
Tos,
|
||||
Pp,
|
||||
Cancel,
|
||||
Continue,
|
||||
ContinueAll,
|
||||
}
|
||||
|
||||
impl Focus {
|
||||
pub fn next(&mut self, order: &[Focus]) {
|
||||
if let Some(pos) = order.iter().position(|&f| f == *self) {
|
||||
let next_pos = (pos + 1) % order.len();
|
||||
*self = order[next_pos];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, order: &[Focus]) {
|
||||
if let Some(pos) = order.iter().position(|&f| f == *self) {
|
||||
let prev_pos = if pos == 0 { order.len() - 1 } else { pos - 1 };
|
||||
*self = order[prev_pos];
|
||||
}
|
||||
}
|
||||
}
|
||||
4
iota-core/src/terms/mod.rs
Normal file
4
iota-core/src/terms/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub mod buttons;
|
||||
pub mod doc;
|
||||
pub mod focus;
|
||||
pub mod terms_getter;
|
||||
105
iota-core/src/terms/terms_getter.rs
Executable file
105
iota-core/src/terms/terms_getter.rs
Executable file
|
|
@ -0,0 +1,105 @@
|
|||
use json::JsonValue::Object;
|
||||
|
||||
use crate::terms::doc::Doc;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Type {
|
||||
EULA,
|
||||
TOS,
|
||||
PP,
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub fn to_str(&self) -> &str {
|
||||
match self {
|
||||
Self::EULA => "eula",
|
||||
Self::TOS => "tos",
|
||||
Self::PP => "privacy",
|
||||
}
|
||||
}
|
||||
pub fn to_string(&self) -> String {
|
||||
match self {
|
||||
Self::EULA => "End User License Agreement".to_string(),
|
||||
Self::TOS => "Terms of Service".to_string(),
|
||||
Self::PP => "Privacy Policy".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_link(terms_type: Type) -> String {
|
||||
format!("https://legal.tensamin.net/{}/", terms_type.to_str())
|
||||
}
|
||||
pub fn get_newest_link(terms_type: Type) -> String {
|
||||
format!("https://legal.tensamin.net/{}/newest/", terms_type.to_str())
|
||||
}
|
||||
|
||||
pub async fn get_current_docs() -> Option<(Doc, Doc, Doc)> {
|
||||
let body = reqwest::get("https://legal.tensamin.net/api/current/")
|
||||
.await
|
||||
.ok()?
|
||||
.text()
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
let json = json::parse(&body).ok()?;
|
||||
|
||||
if let Object(eula) = &json["eula"] {
|
||||
if let Object(tos) = &json["tos"] {
|
||||
if let Object(pp) = &json["pp"] {
|
||||
Some((
|
||||
Doc::from_json(Type::EULA, eula.clone())?,
|
||||
Doc::from_json(Type::TOS, tos.clone())?,
|
||||
Doc::from_json(Type::PP, pp.clone())?,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_newest_docs() -> Option<(Doc, Doc, Doc)> {
|
||||
let body = reqwest::get("https://legal.tensamin.net/api/newest/")
|
||||
.await
|
||||
.ok()?
|
||||
.text()
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
let json = json::parse(&body).ok()?;
|
||||
|
||||
if let Object(eula) = &json["eula"] {
|
||||
if let Object(tos) = &json["tos"] {
|
||||
if let Object(pp) = &json["pp"] {
|
||||
Some((
|
||||
Doc::from_json(Type::EULA, eula.clone())?,
|
||||
Doc::from_json(Type::TOS, tos.clone())?,
|
||||
Doc::from_json(Type::PP, pp.clone())?,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub async fn get_terms(terms_type: Type) -> Option<String> {
|
||||
let body = reqwest::get(format!(
|
||||
"https://legal.tensamin.net/api/text/{}/",
|
||||
terms_type.to_str()
|
||||
))
|
||||
.await
|
||||
.ok()?
|
||||
.text()
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
Some(body)
|
||||
}
|
||||
Loading…
Reference in a new issue