Improve UI

This commit is contained in:
Bernd Schoolmann 2023-12-28 01:09:49 +01:00
parent 000f9e515d
commit cff906922a
No known key found for this signature in database
7 changed files with 175 additions and 12 deletions

View File

@ -192,6 +192,8 @@ func handleVaultStatus(request messages.IPCMessage, cfg *config.Config, vault *v
vaultStatus.NumberOfNotes = len(vault.GetNotes())
vaultStatus.LastSynced = vault.GetLastSynced()
vaultStatus.WebsockedConnected = vault.IsWebsocketConnected()
vaultStatus.PinSet = cfg.HasPin()
vaultStatus.LoggedIn = cfg.IsLoggedIn()
response, err = messages.IPCMessageFromPayload(vaultStatus)
return
}

View File

@ -113,7 +113,9 @@ var statusCmd = &cobra.Command{
fmt.Println(" \"loginEntries\":", status.NumberOfLogins, ",")
fmt.Println(" \"noteEntries\":", status.NumberOfNotes, ",")
fmt.Println(" \"lastSynced\": \"" + time.Unix(status.LastSynced, 0).String() + "\",")
fmt.Println(" \"websocketConnected\":", status.WebsockedConnected)
fmt.Println(" \"websocketConnected\":", status.WebsockedConnected, ",")
fmt.Println(" \"pinSet\":", status.PinSet, ",")
fmt.Println(" \"loggedIn\":", status.LoggedIn)
fmt.Println("}")
default:
println("Wrong response type")

View File

@ -22,6 +22,8 @@ type VaultStatusRequest struct {
type VaultStatusResponse struct {
Locked bool
LoggedIn bool
PinSet bool
NumberOfLogins int
NumberOfNotes int
LastSynced int64

62
ui/components.py Normal file
View File

@ -0,0 +1,62 @@
from gi.repository import Gtk
def status_icon_ok(icon_name):
imagebox = Gtk.Box()
imagebox.set_orientation(Gtk.Orientation.VERTICAL)
imagebox.set_halign(Gtk.Align.CENTER)
imagebox.set_valign(Gtk.Align.CENTER)
image = Gtk.Image()
image.get_style_context().add_class("status-icon")
image.get_style_context().add_class("ok-icon")
image.set_from_icon_name(icon_name)
imagebox.append(image)
return imagebox
def status_icon_error(icon_name):
imagebox = Gtk.Box()
imagebox.set_orientation(Gtk.Orientation.VERTICAL)
imagebox.set_halign(Gtk.Align.CENTER)
imagebox.set_valign(Gtk.Align.CENTER)
image = Gtk.Image()
image.get_style_context().add_class("status-icon")
image.get_style_context().add_class("error-icon")
image.set_from_icon_name(icon_name)
imagebox.append(image)
return imagebox
def status_icon_warning(icon_name):
imagebox = Gtk.Box()
imagebox.set_orientation(Gtk.Orientation.VERTICAL)
imagebox.set_halign(Gtk.Align.CENTER)
imagebox.set_valign(Gtk.Align.CENTER)
image = Gtk.Image()
image.get_style_context().add_class("status-icon")
image.get_style_context().add_class("warning-icon")
image.set_from_icon_name(icon_name)
imagebox.append(image)
return imagebox
class StatusIcon(Gtk.Box):
def __init__(self):
super().__init__()
self.icon_name = None
self.status = None
def set_icon(self, icon_name, status):
if self.icon_name == icon_name and self.status == status:
return
self.icon_name = icon_name
self.status = status
while self.get_first_child() != None:
self.remove(self.get_first_child())
if status == "ok":
self.append(status_icon_ok(icon_name))
elif status == "error":
self.append(status_icon_error(icon_name))
elif status == "warning":
self.append(status_icon_warning(icon_name))
else:
raise Exception("Invalid status", status)

View File

@ -6,9 +6,13 @@ import monitors.dbus_autofill_monitor
import sys
import goldwarden
from threading import Thread
import os
isflatpak = os.path.exists("/.flatpak-info")
pathprefix = "/app/bin/" if isflatpak else "./"
try:
subprocess.Popen(["python3", "/app/bin/background.py"], start_new_session=True)
subprocess.Popen(["python3", f'{pathprefix}background.py'], start_new_session=True)
except:
pass
@ -16,9 +20,9 @@ is_hidden = "--hidden" in sys.argv
if not is_hidden:
try:
subprocess.Popen(["python3", "/app/bin/settings.py"], start_new_session=True)
subprocess.Popen(["python3", f'{pathprefix}settings.py'], start_new_session=True)
except:
subprocess.Popen(["python3", "./settings.py"], start_new_session=True)
subprocess.Popen(["python3", f'{pathprefix}settings.py'], start_new_session=True)
pass
try:
@ -40,7 +44,7 @@ thread = Thread(target=run_daemon)
thread.start()
def on_autofill():
subprocess.Popen(["python3", "/app/bin/autofill.py"], start_new_session=True)
subprocess.Popen(["python3", f'{pathprefix}autofill.py'], start_new_session=True)
monitors.dbus_autofill_monitor.on_autofill = lambda: on_autofill()
monitors.dbus_autofill_monitor.run_daemon()

View File

@ -5,10 +5,11 @@ gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
import gc
from gi.repository import Gtk, Adw, GLib
from gi.repository import Gtk, Adw, GLib, Gdk
import goldwarden
from threading import Thread
import subprocess
import components
class SettingsWinvdow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
@ -31,6 +32,9 @@ class SettingsWinvdow(Gtk.ApplicationWindow):
self.ssh_row.set_subtitle("Listening at ~/.goldwarden-ssh-agent.sock")
self.preferences_group.add(self.ssh_row)
self.icon = components.status_icon_ok("emblem-default")
self.ssh_row.add_prefix(self.icon)
self.login_with_device = Adw.ActionRow()
self.login_with_device.set_title("Login with device")
self.login_with_device.set_subtitle("Waiting for requests...")
@ -50,6 +54,10 @@ class SettingsWinvdow(Gtk.ApplicationWindow):
self.autofill_row.set_subtitle("Unavailable, please set up a shortcut in your desktop environment (README)")
self.shortcut_preferences_group.add(self.autofill_row)
self.autofill_icon = components.StatusIcon()
self.autofill_icon.set_icon("dialog-warning", "warning")
self.autofill_row.add_prefix(self.autofill_icon)
self.copy_username_shortcut_row = Adw.ActionRow()
self.copy_username_shortcut_row.set_title("Copy Username Shortcut")
self.copy_username_shortcut_row.set_subtitle("U")
@ -69,9 +77,14 @@ class SettingsWinvdow(Gtk.ApplicationWindow):
self.status_row.set_subtitle("Locked")
self.vault_status_preferences_group.add(self.status_row)
self.vault_status_icon = components.StatusIcon()
self.vault_status_icon.set_icon("dialog-error", "error")
self.status_row.add_prefix(self.vault_status_icon)
self.last_sync_row = Adw.ActionRow()
self.last_sync_row.set_title("Last Sync")
self.last_sync_row.set_subtitle("Never")
self.last_sync_row.set_icon_name("emblem-synchronizing-symbolic")
self.vault_status_preferences_group.add(self.last_sync_row)
self.websocket_connected_row = Adw.ActionRow()
@ -79,14 +92,20 @@ class SettingsWinvdow(Gtk.ApplicationWindow):
self.websocket_connected_row.set_subtitle("False")
self.vault_status_preferences_group.add(self.websocket_connected_row)
self.websocket_connected_status_icon = components.StatusIcon()
self.websocket_connected_status_icon.set_icon("dialog-error", "error")
self.websocket_connected_row.add_prefix(self.websocket_connected_status_icon)
self.login_row = Adw.ActionRow()
self.login_row.set_title("Vault Login Entries")
self.login_row.set_subtitle("0")
self.login_row.set_icon_name("dialog-password-symbolic")
self.vault_status_preferences_group.add(self.login_row)
self.notes_row = Adw.ActionRow()
self.notes_row.set_title("Vault Notes")
self.notes_row.set_subtitle("0")
self.notes_row.set_icon_name("emblem-documents-symbolic")
self.vault_status_preferences_group.add(self.notes_row)
self.action_preferences_group = Adw.PreferencesGroup()
@ -142,21 +161,55 @@ class SettingsWinvdow(Gtk.ApplicationWindow):
pin_set = goldwarden.is_pin_enabled()
status = goldwarden.get_vault_status()
if status != None:
if pin_set:
self.unlock_button.set_sensitive(True)
else:
self.unlock_button.set_sensitive(False)
logged_in = status["loggedIn"]
if logged_in:
self.preferences_group.set_visible(True)
self.shortcut_preferences_group.set_visible(True)
self.autotype_button.set_visible(True)
self.login_row.set_sensitive(True)
self.notes_row.set_sensitive(True)
self.websocket_connected_row.set_sensitive(True)
else:
self.preferences_group.set_visible(False)
self.shortcut_preferences_group.set_visible(False)
self.autotype_button.set_visible(False)
self.websocket_connected_row.set_sensitive(False)
self.login_row.set_sensitive(False)
self.notes_row.set_sensitive(False)
locked = status["locked"]
self.login_button.set_sensitive(pin_set and not locked)
self.set_pin_button.set_sensitive(not pin_set or not locked)
self.autotype_button.set_sensitive(not locked)
self.status_row.set_subtitle(str("Unlocked" if not locked else "Locked"))
self.status_row.set_subtitle(str("Logged in" if (logged_in and not locked) else "Logged out") if not locked else "Locked")
if locked or not logged_in:
self.vault_status_icon.set_icon("dialog-warning", "warning")
else:
self.vault_status_icon.set_icon("emblem-default", "ok")
if not logged_in:
self.logout_button.set_sensitive(False)
else:
self.logout_button.set_sensitive(True)
self.login_row.set_subtitle(str(status["loginEntries"]))
self.notes_row.set_subtitle(str(status["noteEntries"]))
self.websocket_connected_row.set_subtitle("Connected" if status["websocketConnected"] else "Disconnected")
if status["websocketConnected"]:
self.websocket_connected_status_icon.set_icon("emblem-default", "ok")
else:
self.websocket_connected_status_icon.set_icon("dialog-error", "error")
self.last_sync_row.set_subtitle(str(status["lastSynced"]))
self.unlock_button.set_sensitive(True)
if status["lastSynced"].startswith("1970"):
self.last_sync_row.set_subtitle("Never")
self.unlock_button.set_label("Unlock" if locked else "Lock")
else:
is_daemon_running = goldwarden.is_daemon_running()
if not is_daemon_running:
self.status_row.set_subtitle("Daemon not running")
self.vault_status_icon.set_icon("dialog-error", "error")
GLib.timeout_add(1000, update_labels)
GLib.timeout_add(1000, update_labels)
@ -177,23 +230,26 @@ class MyApp(Adw.Application):
self.settings_win = SettingsWinvdow(application=app)
self.settings_win.present()
app = MyApp(application_id="com.quexten.Goldwarden")
def show_login():
dialog = Gtk.Dialog(title="Goldwarden")
preference_group = Adw.PreferencesGroup()
preference_group.set_title("Config")
preference_group.set_margin_top(10)
preference_group.set_margin_bottom(10)
preference_group.set_margin_start(10)
preference_group.set_margin_end(10)
dialog.get_content_area().append(preference_group)
api_url_entry = Adw.EntryRow()
api_url_entry.set_title("API Url")
# set value
api_url_entry.set_text("https://api.bitwarden.com/")
api_url_entry.set_text("https://vault.bitwarden.com/api")
preference_group.add(api_url_entry)
identity_url_entry = Adw.EntryRow()
identity_url_entry.set_title("Identity Url")
identity_url_entry.set_text("https://identity.bitwarden.com/")
identity_url_entry.set_text("https://vault.bitwarden.com/identity")
preference_group.add(identity_url_entry)
notification_url_entry = Adw.EntryRow()
@ -239,5 +295,14 @@ def show_login():
dialog.set_modal(True)
dialog.present()
css_provider = Gtk.CssProvider()
css_provider.load_from_path("style.css")
Gtk.StyleContext.add_provider_for_display(
Gdk.Display.get_default(),
css_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
app = MyApp(application_id="com.quexten.Goldwarden.settings")
app.run(sys.argv)

26
ui/style.css Normal file
View File

@ -0,0 +1,26 @@
.status-icon {
min-width: 32px;
min-height: 32px;
border-radius: 9999px;
}
.ok-icon {
color: @green_5;
background-color: alpha(@green_3, .25);
}
.warning-icon {
color: #ae7b03;
background: alpha(@yellow_5, .25);
}
.error-icon {
color: @red_4;
background-color: alpha(@red_2, .25);
}
.accent-icon {
padding: 9px;
color: @blue_4;
background-color: alpha(@blue_3, .25);
}