mirror of
https://github.com/YGGverse/Yoda.git
synced 2025-01-28 12:04:13 +00:00
move item property components to separated mods
This commit is contained in:
parent
2799ce37fe
commit
a7e809bb39
@ -3,15 +3,9 @@ use widget::{form::list::item::value::Value, Widget};
|
||||
|
||||
use crate::app::browser::window::Action;
|
||||
use crate::profile::Profile;
|
||||
use gtk::{
|
||||
gio::{prelude::TlsCertificateExt, TlsCertificate},
|
||||
glib::{gformat, Uri},
|
||||
prelude::IsA,
|
||||
};
|
||||
use gtk::{glib::Uri, prelude::IsA};
|
||||
use std::rc::Rc;
|
||||
|
||||
const DATE_FORMAT: &str = "%Y.%m.%d";
|
||||
|
||||
pub struct Gemini {
|
||||
// profile: Rc<Profile>,
|
||||
widget: Rc<Widget>,
|
||||
@ -20,136 +14,13 @@ pub struct Gemini {
|
||||
impl Gemini {
|
||||
// Construct
|
||||
|
||||
/// Create new `Self` for given Profile
|
||||
/// Create new `Self` for given `Profile`
|
||||
pub fn new(profile: Rc<Profile>, action: Rc<Action>, auth_uri: Uri) -> Self {
|
||||
// Init widget
|
||||
let widget = Rc::new(Widget::new(profile.clone()));
|
||||
|
||||
// Init shared URL string from URI
|
||||
let url = auth_uri.to_string();
|
||||
let auth_url = auth_uri.to_string();
|
||||
|
||||
// Add guest option
|
||||
widget.form.list.append(
|
||||
Value::UseGuestSession,
|
||||
"Guest session",
|
||||
"No identity for this request",
|
||||
None,
|
||||
false,
|
||||
);
|
||||
|
||||
// Add new identity option
|
||||
widget.form.list.append(
|
||||
Value::GenerateNewAuth,
|
||||
"Create new",
|
||||
"Generate long-term certificate",
|
||||
None,
|
||||
false,
|
||||
);
|
||||
|
||||
// Add import existing identity option
|
||||
widget.form.list.append(
|
||||
Value::ImportPem,
|
||||
"Import identity",
|
||||
"Use existing certificate",
|
||||
None,
|
||||
false,
|
||||
);
|
||||
|
||||
// Collect identities as options from profile database
|
||||
// * memory cache synced also and could be faster @TODO
|
||||
match profile.identity.gemini.database.records() {
|
||||
Ok(identities) => {
|
||||
for identity in identities {
|
||||
// Get certificate details
|
||||
let certificate = match TlsCertificate::from_pem(&identity.pem) {
|
||||
Ok(certificate) => certificate,
|
||||
Err(reason) => todo!("{reason}"),
|
||||
};
|
||||
|
||||
// Init tooltip components
|
||||
let mut tooltip = "<b>Certificate</b>\n".to_string();
|
||||
|
||||
if let Some(subject_name) = certificate.subject_name() {
|
||||
tooltip
|
||||
.push_str(&format!("\n<small><b>subject</b>\n{subject_name}</small>"));
|
||||
}
|
||||
|
||||
if let Some(issuer_name) = certificate.issuer_name() {
|
||||
tooltip.push_str(&format!("\n<small><b>issuer</b>\n{issuer_name}</small>"));
|
||||
}
|
||||
|
||||
if let Some(not_valid_before) = certificate.not_valid_before() {
|
||||
if let Ok(timestamp) = not_valid_before.format_iso8601() {
|
||||
tooltip.push_str(&format!(
|
||||
"\n<small><b>valid after</b>\n{timestamp}</small>"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(not_valid_after) = certificate.not_valid_after() {
|
||||
if let Ok(timestamp) = not_valid_after.format_iso8601() {
|
||||
tooltip.push_str(&format!(
|
||||
"\n<small><b>valid before</b>\n{timestamp}</small>"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Collect scope info
|
||||
let mut scope = Vec::new();
|
||||
|
||||
for auth in profile
|
||||
.identity
|
||||
.gemini
|
||||
.auth
|
||||
.database
|
||||
.records_scope(None)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|this| this.profile_identity_gemini_id == identity.id)
|
||||
{
|
||||
scope.push(format!("<small>{}</small>", auth.scope.clone()))
|
||||
}
|
||||
|
||||
if !scope.is_empty() {
|
||||
tooltip.push_str(&format!("\n\n<b>Scope</b>\n\n{}", scope.join("\n")));
|
||||
}
|
||||
|
||||
// Append record option
|
||||
widget.form.list.append(
|
||||
Value::ProfileIdentityGeminiId(identity.id),
|
||||
// title
|
||||
&certificate
|
||||
.subject_name()
|
||||
.unwrap_or(gformat!("Unknown"))
|
||||
.replace("CN=", ""), // trim prefix
|
||||
// subtitle
|
||||
&format!(
|
||||
"{} - {} | scope: {}",
|
||||
certificate
|
||||
.not_valid_before()
|
||||
.unwrap() // @TODO
|
||||
.format(DATE_FORMAT)
|
||||
.unwrap(),
|
||||
certificate
|
||||
.not_valid_after()
|
||||
.unwrap() // @TODO
|
||||
.format(DATE_FORMAT)
|
||||
.unwrap(),
|
||||
scope.len(),
|
||||
),
|
||||
Some(&tooltip),
|
||||
profile
|
||||
.identity
|
||||
.gemini
|
||||
.auth
|
||||
.memory
|
||||
.match_scope(&url)
|
||||
.is_some_and(|auth| auth.profile_identity_gemini_id == identity.id), // is selected
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => todo!("{e}"),
|
||||
} // @TODO separate markup
|
||||
// Init widget
|
||||
let widget = Rc::new(Widget::new(profile.clone(), &auth_url));
|
||||
|
||||
// Init events
|
||||
widget.on_apply({
|
||||
@ -158,8 +29,8 @@ impl Gemini {
|
||||
// Get option match user choice
|
||||
let option = match response {
|
||||
Value::ProfileIdentityGeminiId(value) => Some(value),
|
||||
Value::UseGuestSession => None,
|
||||
Value::GenerateNewAuth => Some(
|
||||
Value::GuestSession => None,
|
||||
Value::GeneratePem => Some(
|
||||
match profile
|
||||
.identity
|
||||
.gemini
|
||||
@ -189,14 +60,14 @@ impl Gemini {
|
||||
.identity
|
||||
.gemini
|
||||
.auth
|
||||
.apply(profile_identity_gemini_id, &url)
|
||||
.apply(profile_identity_gemini_id, &auth_url)
|
||||
{
|
||||
todo!("{}", reason.to_string())
|
||||
};
|
||||
}
|
||||
// Remove all identity auths for `auth_uri`
|
||||
None => {
|
||||
if let Err(reason) = profile.identity.gemini.auth.remove_scope(&url) {
|
||||
if let Err(reason) = profile.identity.gemini.auth.remove_scope(&auth_url) {
|
||||
todo!("{}", reason.to_string())
|
||||
};
|
||||
}
|
||||
|
@ -33,12 +33,12 @@ impl Widget {
|
||||
// Constructors
|
||||
|
||||
/// Create new `Self`
|
||||
pub fn new(profile: Rc<Profile>) -> Self {
|
||||
pub fn new(profile: Rc<Profile>, auth_url: &str) -> Self {
|
||||
// Init actions
|
||||
let action = Rc::new(Action::new());
|
||||
|
||||
// Init child container
|
||||
let form = Rc::new(Form::new(profile, action.clone()));
|
||||
let form = Rc::new(Form::new(profile, action.clone(), auth_url));
|
||||
|
||||
// Init main widget
|
||||
let alert_dialog = AlertDialog::builder()
|
||||
|
@ -32,10 +32,10 @@ impl Form {
|
||||
// Constructors
|
||||
|
||||
/// Create new `Self`
|
||||
pub fn new(profile: Rc<Profile>, action: Rc<Action>) -> Self {
|
||||
pub fn new(profile: Rc<Profile>, action: Rc<Action>, auth_url: &str) -> Self {
|
||||
// Init components
|
||||
let file = Rc::new(File::new(action.clone()));
|
||||
let list = Rc::new(List::new());
|
||||
let list = Rc::new(List::new(profile.clone(), auth_url));
|
||||
let name = Rc::new(Name::new(action.clone()));
|
||||
let save = Rc::new(Save::new(profile.clone()));
|
||||
let drop = Rc::new(Drop::new(profile.clone(), action.clone(), list.clone()));
|
||||
@ -61,7 +61,7 @@ impl Form {
|
||||
let update = action.update.clone();
|
||||
move |item| {
|
||||
// Change name entry visibility
|
||||
name.update(matches!(item, Value::GenerateNewAuth));
|
||||
name.update(matches!(item, Value::GeneratePem));
|
||||
|
||||
// Change file choose button visibility
|
||||
file.update(matches!(item, Value::ImportPem));
|
||||
@ -103,7 +103,7 @@ impl Form {
|
||||
/// Validate `Self` components match current selection
|
||||
pub fn is_applicable(&self) -> bool {
|
||||
match self.list.selected_item().value_enum() {
|
||||
Value::GenerateNewAuth => self.name.is_valid(),
|
||||
Value::GeneratePem => self.name.is_valid(),
|
||||
Value::ImportPem => self.file.is_valid(),
|
||||
Value::ProfileIdentityGeminiId(_) => !self.list.selected_item().is_active(),
|
||||
_ => true,
|
||||
|
@ -1,6 +1,9 @@
|
||||
pub mod item;
|
||||
use std::rc::Rc;
|
||||
|
||||
use item::{value::Value, Item};
|
||||
|
||||
use crate::profile::Profile;
|
||||
use gtk::{
|
||||
gdk::Cursor,
|
||||
gio::{
|
||||
@ -20,10 +23,33 @@ impl List {
|
||||
// Constructors
|
||||
|
||||
/// Create new `Self`
|
||||
pub fn new() -> Self {
|
||||
// Init `ListStore` with custom `DropDown` properties
|
||||
pub fn new(profile: Rc<Profile>, auth_url: &str) -> Self {
|
||||
// Init model
|
||||
let list_store = ListStore::new::<Item>();
|
||||
|
||||
list_store.append(&Item::new_guest_session());
|
||||
list_store.append(&Item::new_generate_pem());
|
||||
list_store.append(&Item::new_import_pem());
|
||||
|
||||
// Append identities from profile database
|
||||
// * memory cache synced also and could be faster @TODO
|
||||
match profile.identity.gemini.database.records() {
|
||||
Ok(identities) => {
|
||||
for identity in identities {
|
||||
match Item::new_profile_identity_gemini_id(
|
||||
profile.clone(),
|
||||
identity.id,
|
||||
&identity.pem,
|
||||
auth_url,
|
||||
) {
|
||||
Ok(item) => list_store.append(&item),
|
||||
Err(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => todo!(),
|
||||
}
|
||||
|
||||
// Setup item factory
|
||||
// * wanted only to append items after `DropDown` init
|
||||
let factory = SignalListItemFactory::new();
|
||||
@ -99,6 +125,13 @@ impl List {
|
||||
.factory(&factory)
|
||||
.build();
|
||||
|
||||
// Select active record
|
||||
dropdown.set_selected(
|
||||
list_store
|
||||
.find_with_equal_func(|item| item.dynamic_cast_ref::<Item>().unwrap().is_active())
|
||||
.unwrap(),
|
||||
); // @TODO panic or handle?
|
||||
|
||||
// Return activated `Self`
|
||||
Self {
|
||||
list_store,
|
||||
@ -108,25 +141,6 @@ impl List {
|
||||
|
||||
// Actions
|
||||
|
||||
/// Append new item
|
||||
pub fn append(
|
||||
&self,
|
||||
value: Value,
|
||||
title: &str,
|
||||
subtitle: &str,
|
||||
tooltip: Option<&str>,
|
||||
is_active: bool,
|
||||
) {
|
||||
let item = Item::new(value, title, subtitle, tooltip, is_active);
|
||||
|
||||
self.list_store.append(&item);
|
||||
|
||||
if is_active {
|
||||
self.dropdown
|
||||
.set_selected(self.list_store.find(&item).unwrap()); // @TODO panic or handle?
|
||||
}
|
||||
}
|
||||
|
||||
/// Find list item by `value` (stores ID)
|
||||
/// * return `position` found
|
||||
pub fn find(&self, value: i64) -> Option<u32> {
|
||||
|
@ -1,8 +1,21 @@
|
||||
mod error;
|
||||
mod imp;
|
||||
mod is_active;
|
||||
mod subtitle;
|
||||
mod title;
|
||||
mod tooltip;
|
||||
pub mod value;
|
||||
|
||||
use gtk::glib::{self, Object};
|
||||
use value::Value;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub use error::Error;
|
||||
pub use value::Value;
|
||||
|
||||
use crate::profile::Profile;
|
||||
use gtk::{
|
||||
gio::TlsCertificate,
|
||||
glib::{self, Object},
|
||||
};
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct Item(ObjectSubclass<imp::Item>);
|
||||
@ -10,47 +23,105 @@ glib::wrapper! {
|
||||
|
||||
// C-type property `value` conversion for `Item`
|
||||
// * values > 0 reserved for `profile_identity_gemini_id`
|
||||
const G_VALUE_GENERATE_NEW_AUTH: i64 = 0;
|
||||
const G_VALUE_GENERATE_PEM: i64 = 0;
|
||||
const G_VALUE_IMPORT_PEM: i64 = -1;
|
||||
const G_VALUE_USE_GUEST_SESSION: i64 = -2;
|
||||
const G_VALUE_GUEST_SESSION: i64 = -2;
|
||||
|
||||
impl Item {
|
||||
// Constructors
|
||||
|
||||
/// Create new `GObject`
|
||||
pub fn new(
|
||||
value: Value,
|
||||
title: &str,
|
||||
subtitle: &str,
|
||||
tooltip: Option<&str>,
|
||||
is_active: bool,
|
||||
) -> Self {
|
||||
pub fn new_guest_session() -> Self {
|
||||
Object::builder()
|
||||
.property(
|
||||
"value",
|
||||
match value {
|
||||
Value::GenerateNewAuth => G_VALUE_GENERATE_NEW_AUTH,
|
||||
Value::ImportPem => G_VALUE_IMPORT_PEM,
|
||||
Value::UseGuestSession => G_VALUE_USE_GUEST_SESSION,
|
||||
Value::ProfileIdentityGeminiId(value) => value,
|
||||
},
|
||||
)
|
||||
.property("title", title)
|
||||
.property("subtitle", subtitle)
|
||||
.property("tooltip", tooltip.unwrap_or_default())
|
||||
.property("is_active", is_active)
|
||||
.property("value", G_VALUE_GUEST_SESSION)
|
||||
.property("title", "Guest session")
|
||||
.property("subtitle", "No identity for this request")
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn new_generate_pem() -> Self {
|
||||
Object::builder()
|
||||
.property("value", G_VALUE_GENERATE_PEM)
|
||||
.property("title", "Create new")
|
||||
.property("subtitle", "Generate long-term certificate")
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn new_import_pem() -> Self {
|
||||
Object::builder()
|
||||
.property("value", G_VALUE_IMPORT_PEM)
|
||||
.property("title", "Import identity")
|
||||
.property("subtitle", "Use existing certificate")
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn new_profile_identity_gemini_id(
|
||||
profile: Rc<Profile>,
|
||||
profile_identity_gemini_id: i64,
|
||||
pem: &str,
|
||||
auth_url: &str,
|
||||
) -> Result<Self, Error> {
|
||||
match TlsCertificate::from_pem(pem) {
|
||||
Ok(certificate) => {
|
||||
// Collect shared certificate scope
|
||||
let scope = scope(profile.clone(), profile_identity_gemini_id);
|
||||
|
||||
// Build GObject
|
||||
Ok(Object::builder()
|
||||
.property("value", profile_identity_gemini_id)
|
||||
.property(
|
||||
"title",
|
||||
title::new_for_profile_identity_gemini_id(&certificate),
|
||||
)
|
||||
.property(
|
||||
"subtitle",
|
||||
subtitle::new_for_profile_identity_gemini_id(&certificate, &scope),
|
||||
)
|
||||
.property(
|
||||
"tooltip",
|
||||
tooltip::new_for_profile_identity_gemini_id(&certificate, &scope),
|
||||
)
|
||||
.property(
|
||||
"is_active",
|
||||
is_active::new_for_profile_identity_gemini_id(
|
||||
profile,
|
||||
profile_identity_gemini_id,
|
||||
auth_url,
|
||||
),
|
||||
)
|
||||
.build())
|
||||
}
|
||||
Err(e) => Err(Error::TlsCertificate(e)),
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
/// Get `value` as enum `Value`
|
||||
/// Get `Self` C-value as `Value`
|
||||
pub fn value_enum(&self) -> Value {
|
||||
match self.value() {
|
||||
G_VALUE_GENERATE_NEW_AUTH => Value::GenerateNewAuth,
|
||||
G_VALUE_GENERATE_PEM => Value::GeneratePem,
|
||||
G_VALUE_GUEST_SESSION => Value::GuestSession,
|
||||
G_VALUE_IMPORT_PEM => Value::ImportPem,
|
||||
G_VALUE_USE_GUEST_SESSION => Value::UseGuestSession,
|
||||
value => Value::ProfileIdentityGeminiId(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tools
|
||||
|
||||
fn scope(profile: Rc<Profile>, profile_identity_gemini_id: i64) -> Vec<String> {
|
||||
let mut scope = Vec::new();
|
||||
for auth in profile
|
||||
.identity
|
||||
.gemini
|
||||
.auth
|
||||
.database
|
||||
.records_scope(None)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|this| this.profile_identity_gemini_id == profile_identity_gemini_id)
|
||||
{
|
||||
scope.push(auth.scope.clone())
|
||||
}
|
||||
scope
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
TlsCertificate(gtk::glib::Error),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::TlsCertificate(e) => {
|
||||
write!(f, "TLS certificate error `{e}`")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
use crate::profile::Profile;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub fn new_for_profile_identity_gemini_id(
|
||||
profile: Rc<Profile>,
|
||||
profile_identity_gemini_id: i64,
|
||||
auth_url: &str,
|
||||
) -> bool {
|
||||
profile
|
||||
.identity
|
||||
.gemini
|
||||
.auth
|
||||
.memory
|
||||
.match_scope(&auth_url)
|
||||
.is_some_and(|auth| auth.profile_identity_gemini_id == profile_identity_gemini_id)
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
use gtk::{gio::TlsCertificate, prelude::TlsCertificateExt};
|
||||
|
||||
const DATE_FORMAT: &str = "%Y.%m.%d";
|
||||
|
||||
pub fn new_for_profile_identity_gemini_id(
|
||||
certificate: &TlsCertificate,
|
||||
scope: &[String],
|
||||
) -> String {
|
||||
format!(
|
||||
"{} - {} | scope: {}",
|
||||
certificate
|
||||
.not_valid_before()
|
||||
.unwrap() // @TODO
|
||||
.format(DATE_FORMAT)
|
||||
.unwrap(),
|
||||
certificate
|
||||
.not_valid_after()
|
||||
.unwrap() // @TODO
|
||||
.format(DATE_FORMAT)
|
||||
.unwrap(),
|
||||
scope.len(),
|
||||
)
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
use gtk::{gio::TlsCertificate, glib::gformat, prelude::TlsCertificateExt};
|
||||
|
||||
pub fn new_for_profile_identity_gemini_id(certificate: &TlsCertificate) -> String {
|
||||
certificate
|
||||
.subject_name()
|
||||
.unwrap_or(gformat!("Unknown"))
|
||||
.replace("CN=", "")
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
use gtk::{gio::TlsCertificate, prelude::TlsCertificateExt};
|
||||
|
||||
pub fn new_for_profile_identity_gemini_id(
|
||||
certificate: &TlsCertificate,
|
||||
scope: &Vec<String>,
|
||||
) -> String {
|
||||
let mut tooltip = "<b>Certificate</b>\n".to_string();
|
||||
|
||||
if let Some(subject_name) = certificate.subject_name() {
|
||||
tooltip.push_str(&format!("\n<small><b>subject</b>\n{subject_name}</small>"));
|
||||
}
|
||||
|
||||
if let Some(issuer_name) = certificate.issuer_name() {
|
||||
tooltip.push_str(&format!("\n<small><b>issuer</b>\n{issuer_name}</small>"));
|
||||
}
|
||||
|
||||
if let Some(not_valid_before) = certificate.not_valid_before() {
|
||||
if let Ok(timestamp) = not_valid_before.format_iso8601() {
|
||||
tooltip.push_str(&format!("\n<small><b>valid after</b>\n{timestamp}</small>"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(not_valid_after) = certificate.not_valid_after() {
|
||||
if let Ok(timestamp) = not_valid_after.format_iso8601() {
|
||||
tooltip.push_str(&format!(
|
||||
"\n<small><b>valid before</b>\n{timestamp}</small>"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if !scope.is_empty() {
|
||||
tooltip.push_str("\n\n<b>Scope</b>\n");
|
||||
|
||||
for path in scope {
|
||||
tooltip.push_str(&format!("\n{}", path));
|
||||
}
|
||||
}
|
||||
|
||||
tooltip
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
#[derive(Debug)]
|
||||
pub enum Value {
|
||||
GenerateNewAuth,
|
||||
GeneratePem,
|
||||
GuestSession,
|
||||
ImportPem,
|
||||
ProfileIdentityGeminiId(i64),
|
||||
UseGuestSession,
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user