mirror of
https://github.com/quexten/goldwarden.git
synced 2025-02-03 13:28:37 +01:00
Improve UI
This commit is contained in:
parent
000f9e515d
commit
cff906922a
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
@ -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
62
ui/components.py
Normal 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)
|
12
ui/main.py
12
ui/main.py
@ -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()
|
||||||
|
@ -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
26
ui/style.css
Normal 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);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user