33: Gtk::Box(Gtk::Orientation::VERTICAL, 6),
35 topBar_(Gtk::Orientation::HORIZONTAL, 8),
38 stateBox_(Gtk::Orientation::HORIZONTAL, 0),
47 auto cssProvider = Gtk::CssProvider::create();
48 Gtk::StyleContext::add_provider_for_display(
49 Gdk::Display::get_default(),
51 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
74 buffer_ = Gtk::TextBuffer::create();
90 "<b>Protezione avanzata AES-256-GCM</b> — "
91 "Le password sono cifrate con AES-256-GCM e la chiave è derivata dalla master password "
92 "tramite PBKDF2-SHA256 (100.000 iterazioni). Il file cifrato include un tag di "
93 "autenticazione: qualsiasi manipolazione esterna renderà il vault permanentemente illeggibile."
100 auto* dangerBanner = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
101 dangerBanner->get_style_context()->add_class(
"security-banner");
102 dangerBanner->get_style_context()->add_class(
"alert-danger");
104 auto* dangerIcon = Gtk::make_managed<Gtk::Label>();
105 dangerIcon->set_text(
"⚠️");
106 dangerBanner->append(*dangerIcon);
108 auto* dangerLabel = Gtk::make_managed<Gtk::Label>();
109 dangerLabel->set_markup(
_(
110 "<b>Attenzione:</b> — L'esportazione del vault salva il file CSV con password in chiaro. "
111 "Salvare il file in un luogo sicuro e non accessibile."
113 dangerLabel->set_wrap(
true);
114 dangerLabel->set_xalign(0.0f);
115 dangerLabel->set_hexpand(
true);
116 dangerBanner->append(*dangerLabel);
122 auto* spacer1 = Gtk::make_managed<Gtk::Box>();
123 spacer1->set_hexpand(
true);
128 stateBox_.get_style_context()->add_class(
"alert-success");
132 addButton_.get_style_context()->add_class(
"suggested-action");
136 importButton_.get_style_context()->add_class(
"suggested-action");
137 auto p = Gtk::CssProvider::create();
139 "button.suggested-action#importBtn {"
140 " background:linear-gradient(to bottom,#33d17a,#26a269);"
141 " color:white;font-weight:bold;"
142 " padding:6px 16px;border-radius:6px;"
143 " border:1px solid #1e8449;"
146 importButton_.get_style_context()->add_provider(p, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
151 auto p = Gtk::CssProvider::create();
153 "button.suggested-action#vaultBtn {"
154 " background:linear-gradient(to bottom,#3584e4,#1c71d8);"
155 " color:white;font-weight:bold;"
156 " padding:6px 16px;border-radius:6px;"
157 " border:1px solid #1558b0;"
160 vaultViewButton_.get_style_context()->add_provider(p, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
165 auto* spacer2 = Gtk::make_managed<Gtk::Box>();
166 spacer2->set_hexpand(
true);
172 searchEntry_.set_placeholder_text(
_(
"Cerca credenziali..."));
176 auto* sep = Gtk::make_managed<Gtk::Separator>(Gtk::Orientation::HORIZONTAL);
186 append(*dangerBanner);
245 if (!parent) {
logSafe(
_(
"Impossibile trovare la finestra padre.\n"));
return; }
248 auto* dialog = Gtk::make_managed<Gtk::Dialog>(
_(
"Crea master password"), *parent,
true);
249 dialog->get_content_area()->set_margin(12);
250 dialog->get_content_area()->set_spacing(8);
252 auto* infoLabel = Gtk::make_managed<Gtk::Label>(
253 _(
"Prima configurazione: scegli una master password.\n"
254 "Minimo 8 caratteri. Non può essere recuperata se dimenticata."));
255 infoLabel->set_wrap(
true);
256 infoLabel->set_xalign(0.0f);
257 infoLabel->get_style_context()->add_class(
"dim-label");
259 auto* entry1 = Gtk::make_managed<Gtk::Entry>();
260 entry1->set_placeholder_text(
_(
"Nuova master password"));
261 entry1->set_visibility(
false);
262 entry1->set_input_purpose(Gtk::InputPurpose::PASSWORD);
264 auto* entry2 = Gtk::make_managed<Gtk::Entry>();
265 entry2->set_placeholder_text(
_(
"Conferma master password"));
266 entry2->set_visibility(
false);
267 entry2->set_input_purpose(Gtk::InputPurpose::PASSWORD);
268 entry2->set_activates_default(
true);
270 auto* strengthLabel = Gtk::make_managed<Gtk::Label>(
"");
271 strengthLabel->set_xalign(0.0f);
273 dialog->get_content_area()->append(*infoLabel);
274 dialog->get_content_area()->append(*entry1);
275 dialog->get_content_area()->append(*entry2);
276 dialog->get_content_area()->append(*strengthLabel);
277 dialog->add_button(
_(
"Annulla"), Gtk::ResponseType::CANCEL);
278 dialog->add_button(
_(
"Crea vault"), Gtk::ResponseType::OK);
279 dialog->set_default_response(Gtk::ResponseType::OK);
281 entry1->signal_changed().connect([entry1, strengthLabel]() {
282 const auto pwd = entry1->get_text();
283 const size_t len = pwd.size();
284 bool hasUpper =
false, hasLower =
false, hasDigit =
false, hasSymbol =
false;
286 if (std::isupper(c)) hasUpper =
true;
287 else if (std::islower(c)) hasLower =
true;
288 else if (std::isdigit(c)) hasDigit =
true;
289 else hasSymbol =
true;
291 const int score = (len >= 8) + (len >= 12) + hasUpper + hasLower + hasDigit + hasSymbol;
293 if (len == 0) markup =
"";
294 else if (score <= 2) markup =
"<span foreground='red'>⚠ Debole</span>";
295 else if (score <= 4) markup =
"<span foreground='orange'>◑ Media</span>";
296 else markup =
"<span foreground='green'>✔ Forte</span>";
297 strengthLabel->set_markup(markup);
300 dialog->signal_response().connect([
this, dialog, entry1, entry2](
int response) {
301 if (response == Gtk::ResponseType::OK) {
302 const auto pwd1 = entry1->get_text();
303 const auto pwd2 = entry2->get_text();
305 logSafe(
_(
"Le password non coincidono.\n"));
306 }
else if (pwd1.size() < 8) {
307 logSafe(
_(
"La master password deve essere di almeno 8 caratteri.\n"));
310 logSafe(
_(
"Vault creato. Master password impostata.\n"));
312 logSafe(
_(
"Errore durante la creazione del vault.\n"));
320 auto* dialog = Gtk::make_managed<Gtk::Dialog>(
_(
"Sblocca vault"), *parent,
true);
321 auto* entry = Gtk::make_managed<Gtk::Entry>();
322 entry->set_placeholder_text(
_(
"Master password"));
323 entry->set_visibility(
false);
324 entry->set_input_purpose(Gtk::InputPurpose::PASSWORD);
325 entry->set_activates_default(
true);
326 dialog->get_content_area()->set_margin(12);
327 dialog->get_content_area()->append(*entry);
328 dialog->add_button(
_(
"Annulla"), Gtk::ResponseType::CANCEL);
329 dialog->add_button(
_(
"Sblocca"), Gtk::ResponseType::OK);
330 dialog->set_default_response(Gtk::ResponseType::OK);
332 dialog->signal_response().connect([
this, dialog, entry](
int response) {
333 if (response == Gtk::ResponseType::OK) {
336 logSafe(
_(
"Vault sbloccato. Precaricamento password in background…\n"));
337 Glib::signal_timeout().connect_once([
this]() {
398 dialog->signal_import_confirmed().connect(
399 [
this, dialog](std::vector<PasswordImportDialog::BulkItem> items) {
401 if (items.empty()) {
delete dialog;
return; }
404 for (
auto& [entry, pwd] : items) {
406 entry.created_at = now;
407 entry.updated_at = now;
408 if (entry.name.empty() && !entry.url.empty()) {
409 auto url = entry.url;
410 auto p = url.find(
"://");
411 if (p != std::string::npos) url = url.substr(p + 3);
412 auto sl = url.find(
'/');
413 if (sl != std::string::npos) url = url.substr(0, sl);
419 logSafe(std::string(
_(
"Import completato: ")) + std::to_string(ok) +
420 std::string(
_(
" importate")) +
421 (fail > 0 ? (
", " + std::to_string(fail) + std::string(
_(
" fallite"))) :
"") +
448 if (count == 0) {
logSafe(
_(
"Nessuna credenziale da eliminare.\n"));
return; }
450 auto* confirm1 =
new Gtk::MessageDialog(
451 *parent,
_(
"Eliminare tutte le credenziali?"),
452 false, Gtk::MessageType::WARNING, Gtk::ButtonsType::OK_CANCEL,
true);
453 confirm1->set_secondary_text(
454 std::string(
_(
"Questa operazione eliminerà ")) + std::to_string(count) +
455 std::string(
_(
" credenziali e non può essere annullata.")));
457 confirm1->signal_response().connect([
this, confirm1, parent](
int r1) {
459 if (r1 != Gtk::ResponseType::OK) {
delete confirm1;
return; }
461 auto* confirm2 =
new Gtk::MessageDialog(
462 *parent,
_(
"Sei sicuro? L'operazione è irreversibile."),
463 false, Gtk::MessageType::ERROR, Gtk::ButtonsType::NONE,
true);
464 confirm2->set_secondary_text(
_(
"Il file cifrato verrà eliminato definitivamente."));
465 confirm2->add_button(
_(
"Annulla"), Gtk::ResponseType::CANCEL);
466 confirm2->add_button(
_(
"Sì, elimina tutto"), Gtk::ResponseType::OK);
467 auto* ok_btn = confirm2->get_widget_for_response(Gtk::ResponseType::OK);
468 if (ok_btn) ok_btn->get_style_context()->add_class(
"destructive-action");
470 confirm2->signal_response().connect([
this, confirm2](
int r2) {
472 if (r2 == Gtk::ResponseType::OK) {
473 logSafe(
_(
"Eliminazione in corso…\n"));
475 logSafe(ok ?
_(
"✔ Tutte le credenziali eliminate.\n")
476 :
_(
"Errore durante l'eliminazione.\n"));
508 if (!alive || !alive->load(std::memory_order_acquire)) return;
509 auto* p = getParentWindow();
511 auto* reveal = new Gtk::MessageDialog(
512 *p, _(
"Password per: ") + entry.name,
513 false, Gtk::MessageType::INFO, Gtk::ButtonsType::CLOSE, true);
514 reveal->set_secondary_text(secret, false);
515 reveal->signal_response().connect([reveal](int) { reveal->hide(); delete reveal; });
529 if (!alive || !alive->load(std::memory_order_acquire)) return;
530 auto* p = getParentWindow();
532 auto* dialog = new PasswordDialog(
533 *p, _(
"Modifica credenziale"), entry, secret);
534 dialog->signal_response().connect([this, dialog, entry](int response) {
535 if (response == Gtk::ResponseType::OK) {
536 PasswordEntry updated = dialog->getEntry();
537 updated.id = entry.id;
538 updated.created_at = entry.created_at;
539 updated.updated_at = vault_.nowIso();
540 if (vault_.updateEntry(updated, dialog->getSecret())) {
541 logSafe(std::string(_(
"Credenziale aggiornata: ")) + updated.name +
"\n");
544 logSafe(_(
"Errore durante l'aggiornamento.\n"));
Gtk::Window * getParentWindow()
Gtk::ScrolledWindow logScrolled_
~PasswordManagerPage() override
Gtk::ScrolledWindow listScrolled_
void onRevealEntry(const PasswordEntry &entry)
void setVaultState(bool unlocked, const std::string &msg="")
void createEntryRow(const PasswordEntry &entry)
Gtk::SearchEntry searchEntry_
BastionGuard::AliveFlag alive_
void refreshEntries(const std::string &filter="")
std::vector< PasswordEntry > entriesCache_
Glib::RefPtr< Gtk::TextBuffer > buffer_
void onDeleteEntry(const PasswordEntry &entry)
Gtk::TextView outputView_
void logSafe(const std::string &msg)
void onEditEntry(const PasswordEntry &entry)