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.NumberOfNotes = len(vault.GetNotes())
vaultStatus.LastSynced = vault.GetLastSynced() vaultStatus.LastSynced = vault.GetLastSynced()
vaultStatus.WebsockedConnected = vault.IsWebsocketConnected() vaultStatus.WebsockedConnected = vault.IsWebsocketConnected()
vaultStatus.PinSet = cfg.HasPin()
vaultStatus.LoggedIn = cfg.IsLoggedIn()
response, err = messages.IPCMessageFromPayload(vaultStatus) response, err = messages.IPCMessageFromPayload(vaultStatus)
return return
} }

View File

@ -113,7 +113,9 @@ var statusCmd = &cobra.Command{
fmt.Println(" \"loginEntries\":", status.NumberOfLogins, ",") fmt.Println(" \"loginEntries\":", status.NumberOfLogins, ",")
fmt.Println(" \"noteEntries\":", status.NumberOfNotes, ",") fmt.Println(" \"noteEntries\":", status.NumberOfNotes, ",")
fmt.Println(" \"lastSynced\": \"" + time.Unix(status.LastSynced, 0).String() + "\",") 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("}") fmt.Println("}")
default: default:
println("Wrong response type") println("Wrong response type")

View File

@ -22,6 +22,8 @@ type VaultStatusRequest struct {
type VaultStatusResponse struct { type VaultStatusResponse struct {
Locked bool Locked bool
LoggedIn bool
PinSet bool
NumberOfLogins int NumberOfLogins int
NumberOfNotes int NumberOfNotes int
LastSynced int64 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 sys
import goldwarden import goldwarden
from threading import Thread from threading import Thread
import os
isflatpak = os.path.exists("/.flatpak-info")
pathprefix = "/app/bin/" if isflatpak else "./"
try: try:
subprocess.Popen(["python3", "/app/bin/background.py"], start_new_session=True) subprocess.Popen(["python3", f'{pathprefix}background.py'], start_new_session=True)
except: except:
pass pass
@ -16,9 +20,9 @@ is_hidden = "--hidden" in sys.argv
if not is_hidden: if not is_hidden:
try: try:
subprocess.Popen(["python3", "/app/bin/settings.py"], start_new_session=True) subprocess.Popen(["python3", f'{pathprefix}settings.py'], start_new_session=True)
except: except:
subprocess.Popen(["python3", "./settings.py"], start_new_session=True) subprocess.Popen(["python3", f'{pathprefix}settings.py'], start_new_session=True)
pass pass
try: try:
@ -40,7 +44,7 @@ thread = Thread(target=run_daemon)
thread.start() thread.start()
def on_autofill(): 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.on_autofill = lambda: on_autofill()
monitors.dbus_autofill_monitor.run_daemon() monitors.dbus_autofill_monitor.run_daemon()

View File

@ -5,10 +5,11 @@ gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1') gi.require_version('Adw', '1')
import gc import gc
from gi.repository import Gtk, Adw, GLib from gi.repository import Gtk, Adw, GLib, Gdk
import goldwarden import goldwarden
from threading import Thread from threading import Thread
import subprocess import subprocess
import components
class SettingsWinvdow(Gtk.ApplicationWindow): class SettingsWinvdow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs): 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.ssh_row.set_subtitle("Listening at ~/.goldwarden-ssh-agent.sock")
self.preferences_group.add(self.ssh_row) 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 = Adw.ActionRow()
self.login_with_device.set_title("Login with device") self.login_with_device.set_title("Login with device")
self.login_with_device.set_subtitle("Waiting for requests...") 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.autofill_row.set_subtitle("Unavailable, please set up a shortcut in your desktop environment (README)")
self.shortcut_preferences_group.add(self.autofill_row) 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 = Adw.ActionRow()
self.copy_username_shortcut_row.set_title("Copy Username Shortcut") self.copy_username_shortcut_row.set_title("Copy Username Shortcut")
self.copy_username_shortcut_row.set_subtitle("U") self.copy_username_shortcut_row.set_subtitle("U")
@ -69,9 +77,14 @@ class SettingsWinvdow(Gtk.ApplicationWindow):
self.status_row.set_subtitle("Locked") self.status_row.set_subtitle("Locked")
self.vault_status_preferences_group.add(self.status_row) 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 = Adw.ActionRow()
self.last_sync_row.set_title("Last Sync") self.last_sync_row.set_title("Last Sync")
self.last_sync_row.set_subtitle("Never") 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.vault_status_preferences_group.add(self.last_sync_row)
self.websocket_connected_row = Adw.ActionRow() self.websocket_connected_row = Adw.ActionRow()
@ -79,14 +92,20 @@ class SettingsWinvdow(Gtk.ApplicationWindow):
self.websocket_connected_row.set_subtitle("False") self.websocket_connected_row.set_subtitle("False")
self.vault_status_preferences_group.add(self.websocket_connected_row) 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 = Adw.ActionRow()
self.login_row.set_title("Vault Login Entries") self.login_row.set_title("Vault Login Entries")
self.login_row.set_subtitle("0") 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.vault_status_preferences_group.add(self.login_row)
self.notes_row = Adw.ActionRow() self.notes_row = Adw.ActionRow()
self.notes_row.set_title("Vault Notes") self.notes_row.set_title("Vault Notes")
self.notes_row.set_subtitle("0") 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.vault_status_preferences_group.add(self.notes_row)
self.action_preferences_group = Adw.PreferencesGroup() self.action_preferences_group = Adw.PreferencesGroup()
@ -142,21 +161,55 @@ class SettingsWinvdow(Gtk.ApplicationWindow):
pin_set = goldwarden.is_pin_enabled() pin_set = goldwarden.is_pin_enabled()
status = goldwarden.get_vault_status() status = goldwarden.get_vault_status()
if status != None: 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"] locked = status["locked"]
self.login_button.set_sensitive(pin_set and not locked) self.login_button.set_sensitive(pin_set and not locked)
self.set_pin_button.set_sensitive(not pin_set or not locked) self.set_pin_button.set_sensitive(not pin_set or not locked)
self.autotype_button.set_sensitive(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.login_row.set_subtitle(str(status["loginEntries"]))
self.notes_row.set_subtitle(str(status["noteEntries"])) self.notes_row.set_subtitle(str(status["noteEntries"]))
self.websocket_connected_row.set_subtitle("Connected" if status["websocketConnected"] else "Disconnected") 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.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") self.unlock_button.set_label("Unlock" if locked else "Lock")
else: else:
is_daemon_running = goldwarden.is_daemon_running() is_daemon_running = goldwarden.is_daemon_running()
if not is_daemon_running: if not is_daemon_running:
self.status_row.set_subtitle("Daemon not 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)
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 = SettingsWinvdow(application=app)
self.settings_win.present() self.settings_win.present()
app = MyApp(application_id="com.quexten.Goldwarden")
def show_login(): def show_login():
dialog = Gtk.Dialog(title="Goldwarden") dialog = Gtk.Dialog(title="Goldwarden")
preference_group = Adw.PreferencesGroup() preference_group = Adw.PreferencesGroup()
preference_group.set_title("Config") 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) dialog.get_content_area().append(preference_group)
api_url_entry = Adw.EntryRow() api_url_entry = Adw.EntryRow()
api_url_entry.set_title("API Url") api_url_entry.set_title("API Url")
# set value # 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) preference_group.add(api_url_entry)
identity_url_entry = Adw.EntryRow() identity_url_entry = Adw.EntryRow()
identity_url_entry.set_title("Identity Url") 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) preference_group.add(identity_url_entry)
notification_url_entry = Adw.EntryRow() notification_url_entry = Adw.EntryRow()
@ -239,5 +295,14 @@ def show_login():
dialog.set_modal(True) dialog.set_modal(True)
dialog.present() 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 = MyApp(application_id="com.quexten.Goldwarden.settings")
app.run(sys.argv) 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);
}