Compare commits
No commits in common. "e3913ab4c9d98cfed702e30b5875073eaa644d84" and "fa228acc3a74125e8ddc5f3a5b4637d0587fe57e" have entirely different histories.
e3913ab4c9
...
fa228acc3a
12
src-tauri/Cargo.lock
generated
12
src-tauri/Cargo.lock
generated
@ -1066,7 +1066,6 @@ dependencies = [
|
|||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"which",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5146,17 +5145,6 @@ dependencies = [
|
|||||||
"windows-metadata",
|
"windows-metadata",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "which"
|
|
||||||
version = "4.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
"libc",
|
|
||||||
"once_cell",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@ -25,7 +25,7 @@ tauri-build = { version = "1.0.4", features = [] }
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tauri = { version = "1.2", features = ["dialog", "dialog-open", "global-shortcut", "os-all", "system-tray"] }
|
tauri = { version = "1.2", features = ["dialog", "os-all", "system-tray"] }
|
||||||
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
|
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
|
||||||
sodiumoxide = "0.2.7"
|
sodiumoxide = "0.2.7"
|
||||||
tokio = { version = ">=1.19", features = ["full"] }
|
tokio = { version = ">=1.19", features = ["full"] }
|
||||||
@ -46,7 +46,6 @@ clap = { version = "3.2.23", features = ["derive"] }
|
|||||||
is-terminal = "0.4.7"
|
is-terminal = "0.4.7"
|
||||||
argon2 = { version = "0.5.0", features = ["std"] }
|
argon2 = { version = "0.5.0", features = ["std"] }
|
||||||
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
||||||
which = "4.4.0"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# by default Tauri runs in production mode
|
# by default Tauri runs in production mode
|
||||||
|
@ -42,7 +42,6 @@ pub fn run() -> tauri::Result<()> {
|
|||||||
ipc::save_credentials,
|
ipc::save_credentials,
|
||||||
ipc::get_config,
|
ipc::get_config,
|
||||||
ipc::save_config,
|
ipc::save_config,
|
||||||
ipc::launch_terminal,
|
|
||||||
])
|
])
|
||||||
.setup(|app| rt::block_on(setup(app)))
|
.setup(|app| rt::block_on(setup(app)))
|
||||||
.build(tauri::generate_context!())?
|
.build(tauri::generate_context!())?
|
||||||
@ -83,7 +82,6 @@ async fn setup(app: &mut App) -> Result<(), Box<dyn Error>> {
|
|||||||
let srv = Server::new(conf.listen_addr, conf.listen_port, app.handle()).await?;
|
let srv = Server::new(conf.listen_addr, conf.listen_port, app.handle()).await?;
|
||||||
|
|
||||||
config::set_auto_launch(conf.start_on_login)?;
|
config::set_auto_launch(conf.start_on_login)?;
|
||||||
config::register_hotkeys(&conf.hotkeys)?;
|
|
||||||
// if session is empty, this is probably the first launch, so don't autohide
|
// if session is empty, this is probably the first launch, so don't autohide
|
||||||
if !conf.start_minimized || is_first_launch {
|
if !conf.start_minimized || is_first_launch {
|
||||||
app.get_window("main")
|
app.get_window("main")
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use std::ffi::OsString;
|
|
||||||
use std::process::Command as ChildCommand;
|
use std::process::Command as ChildCommand;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::process::CommandExt;
|
use std::os::unix::process::CommandExt;
|
||||||
@ -91,28 +90,15 @@ pub fn exec(args: &ArgMatches) -> Result<(), CliError> {
|
|||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
// cmd.exec() never returns if successful
|
let e = cmd.exec(); // never returns if successful
|
||||||
let e = cmd.exec();
|
Err(ExecError::ExecutionFailed(e))?;
|
||||||
match e.kind() {
|
Ok(())
|
||||||
std::io::ErrorKind::NotFound => {
|
|
||||||
let name: OsString = cmd_name.into();
|
|
||||||
Err(ExecError::NotFound(name).into())
|
|
||||||
}
|
|
||||||
e => Err(ExecError::ExecutionFailed(e).into()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
let mut child = match cmd.spawn() {
|
let mut child = cmd.spawn()
|
||||||
Ok(c) => c,
|
.map_err(|e| ExecError::ExecutionFailed(e))?;
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
|
||||||
let name: OsString = cmd_name.into();
|
|
||||||
return Err(ExecError::NotFound(name).into());
|
|
||||||
}
|
|
||||||
Err(e) => return Err(ExecError::ExecutionFailed(e).into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let status = child.wait()
|
let status = child.wait()
|
||||||
.map_err(|e| ExecError::ExecutionFailed(e))?;
|
.map_err(|e| ExecError::ExecutionFailed(e))?;
|
||||||
std::process::exit(status.code().unwrap_or(1));
|
std::process::exit(status.code().unwrap_or(1));
|
||||||
|
@ -5,41 +5,10 @@ use auto_launch::AutoLaunchBuilder;
|
|||||||
use is_terminal::IsTerminal;
|
use is_terminal::IsTerminal;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use tauri::{
|
|
||||||
Manager,
|
|
||||||
GlobalShortcutManager,
|
|
||||||
async_runtime as rt,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct TermConfig {
|
|
||||||
pub name: String,
|
|
||||||
// we call it exec because it isn't always the actual path,
|
|
||||||
// in some cases it's just the name and relies on path-searching
|
|
||||||
// it's a string because it can come from the frontend as json
|
|
||||||
pub exec: String,
|
|
||||||
pub args: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
|
||||||
pub struct Hotkey {
|
|
||||||
pub keys: String,
|
|
||||||
pub enabled: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
|
||||||
pub struct HotkeysConfig {
|
|
||||||
// tauri uses strings to represent keybinds, so we will as well
|
|
||||||
pub show_window: Hotkey,
|
|
||||||
pub launch_terminal: Hotkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
#[serde(default = "default_listen_addr")]
|
#[serde(default = "default_listen_addr")]
|
||||||
@ -52,10 +21,6 @@ pub struct AppConfig {
|
|||||||
pub start_minimized: bool,
|
pub start_minimized: bool,
|
||||||
#[serde(default = "default_start_on_login")]
|
#[serde(default = "default_start_on_login")]
|
||||||
pub start_on_login: bool,
|
pub start_on_login: bool,
|
||||||
#[serde(default = "default_term_config")]
|
|
||||||
pub terminal: TermConfig,
|
|
||||||
#[serde(default = "default_hotkey_config")]
|
|
||||||
pub hotkeys: HotkeysConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -67,8 +32,6 @@ impl Default for AppConfig {
|
|||||||
rehide_ms: default_rehide_ms(),
|
rehide_ms: default_rehide_ms(),
|
||||||
start_minimized: default_start_minimized(),
|
start_minimized: default_start_minimized(),
|
||||||
start_on_login: default_start_on_login(),
|
start_on_login: default_start_on_login(),
|
||||||
terminal: default_term_config(),
|
|
||||||
hotkeys: default_hotkey_config(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,91 +116,6 @@ fn default_listen_port() -> u16 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn default_term_config() -> TermConfig {
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
let shell = if which::which("pwsh.exe").is_ok() {
|
|
||||||
"pwsh.exe".to_string()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
"powershell.exe".to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
let (exec, args) = if cfg!(debug_assertions) {
|
|
||||||
("conhost.exe".to_string(), vec![shell.clone()])
|
|
||||||
} else {
|
|
||||||
(shell.clone(), vec![])
|
|
||||||
};
|
|
||||||
|
|
||||||
TermConfig { name: shell, exec, args }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
for bin in ["gnome-terminal", "konsole"] {
|
|
||||||
if let Ok(_) = which::which(bin) {
|
|
||||||
return TermConfig {
|
|
||||||
name: bin.into(),
|
|
||||||
exec: bin.into(),
|
|
||||||
args: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TermConfig {
|
|
||||||
name: "gnome-terminal".into(),
|
|
||||||
exec: "gnome-terminal".into(),
|
|
||||||
args: vec![],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn default_hotkey_config() -> HotkeysConfig {
|
|
||||||
HotkeysConfig {
|
|
||||||
show_window: Hotkey {keys: "alt+shift+C".into(), enabled: true},
|
|
||||||
launch_terminal: Hotkey {keys: "alt+shift+T".into(), enabled: true},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// note: will panic if called before APP is set
|
|
||||||
pub fn register_hotkeys(hotkeys: &HotkeysConfig) -> tauri::Result<()> {
|
|
||||||
let app = crate::app::APP.get().unwrap();
|
|
||||||
|
|
||||||
let mut manager = app.global_shortcut_manager();
|
|
||||||
manager.unregister_all()?;
|
|
||||||
|
|
||||||
if hotkeys.show_window.enabled {
|
|
||||||
let handle = app.app_handle();
|
|
||||||
manager.register(
|
|
||||||
&hotkeys.show_window.keys,
|
|
||||||
move || {
|
|
||||||
handle.get_window("main")
|
|
||||||
.map(|w| w.show().error_popup("Failed to show"))
|
|
||||||
.ok_or(HandlerError::NoMainWindow)
|
|
||||||
.error_popup("No main window");
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if hotkeys.launch_terminal.enabled {
|
|
||||||
// register() doesn't take an async fn, so we have to use spawn
|
|
||||||
manager.register(
|
|
||||||
&hotkeys.launch_terminal.keys,
|
|
||||||
|| {
|
|
||||||
rt::spawn(async {
|
|
||||||
crate::terminal::launch(false)
|
|
||||||
.await
|
|
||||||
.error_popup("Failed to launch");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn default_listen_addr() -> Ipv4Addr { Ipv4Addr::LOCALHOST }
|
fn default_listen_addr() -> Ipv4Addr { Ipv4Addr::LOCALHOST }
|
||||||
fn default_rehide_ms() -> u64 { 1000 }
|
fn default_rehide_ms() -> u64 { 1000 }
|
||||||
// start minimized and on login only in production mode
|
// start minimized and on login only in production mode
|
||||||
|
@ -81,16 +81,6 @@ impl Session {
|
|||||||
Session::Empty => Err(GetSessionError::CredentialsEmpty),
|
Session::Empty => Err(GetSessionError::CredentialsEmpty),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_get(
|
|
||||||
&self
|
|
||||||
) -> Result<(&BaseCredentials, &SessionCredentials), GetCredentialsError> {
|
|
||||||
match self {
|
|
||||||
Self::Empty => Err(GetCredentialsError::Empty),
|
|
||||||
Self::Locked(_) => Err(GetCredentialsError::Locked),
|
|
||||||
Self::Unlocked{ ref base, ref session } => Ok((base, session))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::convert::AsRef;
|
use std::convert::AsRef;
|
||||||
use std::ffi::OsString;
|
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use strum_macros::AsRefStr;
|
use strum_macros::AsRefStr;
|
||||||
|
|
||||||
@ -95,8 +94,6 @@ pub enum SetupError {
|
|||||||
ServerSetupError(#[from] std::io::Error),
|
ServerSetupError(#[from] std::io::Error),
|
||||||
#[error("Failed to resolve data directory: {0}")]
|
#[error("Failed to resolve data directory: {0}")]
|
||||||
DataDir(#[from] DataDirError),
|
DataDir(#[from] DataDirError),
|
||||||
#[error("Failed to register hotkeys: {0}")]
|
|
||||||
RegisterHotkeys(#[from] tauri::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -219,6 +216,16 @@ pub enum RequestError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Errors encountered while running a subprocess (via creddy exec)
|
||||||
|
#[derive(Debug, ThisError, AsRefStr)]
|
||||||
|
pub enum ExecError {
|
||||||
|
#[error("Please specify a command")]
|
||||||
|
NoCommand,
|
||||||
|
#[error("Failed to execute command: {0}")]
|
||||||
|
ExecutionFailed(#[from] std::io::Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, ThisError, AsRefStr)]
|
#[derive(Debug, ThisError, AsRefStr)]
|
||||||
pub enum CliError {
|
pub enum CliError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
@ -230,33 +237,6 @@ pub enum CliError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Errors encountered while trying to launch a child process
|
|
||||||
#[derive(Debug, ThisError, AsRefStr)]
|
|
||||||
pub enum ExecError {
|
|
||||||
#[error("Please specify a command")]
|
|
||||||
NoCommand,
|
|
||||||
#[error("Executable not found: {0:?}")]
|
|
||||||
NotFound(OsString),
|
|
||||||
#[error("Failed to execute command: {0}")]
|
|
||||||
ExecutionFailed(#[from] std::io::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
GetCredentials(#[from] GetCredentialsError),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, ThisError, AsRefStr)]
|
|
||||||
pub enum LaunchTerminalError {
|
|
||||||
#[error("Could not discover main window")]
|
|
||||||
NoMainWindow,
|
|
||||||
#[error("Failed to communicate with main Creddy window")]
|
|
||||||
IpcFailed(#[from] tauri::Error),
|
|
||||||
#[error("Failed to launch terminal: {0}")]
|
|
||||||
Exec(#[from] ExecError),
|
|
||||||
#[error(transparent)]
|
|
||||||
GetCredentials(#[from] GetCredentialsError),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// Serialize implementations
|
// Serialize implementations
|
||||||
// =========================
|
// =========================
|
||||||
@ -347,33 +327,3 @@ impl Serialize for UnlockError {
|
|||||||
map.end()
|
map.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Serialize for ExecError {
|
|
||||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
||||||
let mut map = serializer.serialize_map(None)?;
|
|
||||||
map.serialize_entry("code", self.as_ref())?;
|
|
||||||
map.serialize_entry("msg", &format!("{self}"))?;
|
|
||||||
|
|
||||||
match self {
|
|
||||||
ExecError::GetCredentials(src) => map.serialize_entry("source", &src)?,
|
|
||||||
_ => serialize_upstream_err(self, &mut map)?,
|
|
||||||
}
|
|
||||||
map.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Serialize for LaunchTerminalError {
|
|
||||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
||||||
let mut map = serializer.serialize_map(None)?;
|
|
||||||
map.serialize_entry("code", self.as_ref())?;
|
|
||||||
map.serialize_entry("msg", &format!("{self}"))?;
|
|
||||||
|
|
||||||
match self {
|
|
||||||
LaunchTerminalError::Exec(src) => map.serialize_entry("source", &src)?,
|
|
||||||
_ => serialize_upstream_err(self, &mut map)?,
|
|
||||||
}
|
|
||||||
map.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -6,7 +6,6 @@ use crate::credentials::{Session,BaseCredentials};
|
|||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
use crate::clientinfo::Client;
|
use crate::clientinfo::Client;
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
use crate::terminal;
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
@ -79,9 +78,3 @@ pub async fn save_config(config: AppConfig, app_state: State<'_, AppState>) -> R
|
|||||||
.map_err(|e| format!("Error saving config: {e}"))?;
|
.map_err(|e| format!("Error saving config: {e}"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn launch_terminal(base: bool) -> Result<(), LaunchTerminalError> {
|
|
||||||
terminal::launch(base).await
|
|
||||||
}
|
|
||||||
|
@ -7,5 +7,4 @@ mod clientinfo;
|
|||||||
mod ipc;
|
mod ipc;
|
||||||
mod state;
|
mod state;
|
||||||
mod server;
|
mod server;
|
||||||
mod terminal;
|
|
||||||
mod tray;
|
mod tray;
|
||||||
|
@ -29,7 +29,6 @@ pub struct AppState {
|
|||||||
pub session: RwLock<Session>,
|
pub session: RwLock<Session>,
|
||||||
pub request_count: RwLock<u64>,
|
pub request_count: RwLock<u64>,
|
||||||
pub open_requests: RwLock<HashMap<u64, Sender<ipc::Approval>>>,
|
pub open_requests: RwLock<HashMap<u64, Sender<ipc::Approval>>>,
|
||||||
pub pending_terminal_request: RwLock<bool>,
|
|
||||||
pub bans: RwLock<std::collections::HashSet<Option<Client>>>,
|
pub bans: RwLock<std::collections::HashSet<Option<Client>>>,
|
||||||
server: RwLock<Server>,
|
server: RwLock<Server>,
|
||||||
pool: sqlx::SqlitePool,
|
pool: sqlx::SqlitePool,
|
||||||
@ -42,7 +41,6 @@ impl AppState {
|
|||||||
session: RwLock::new(session),
|
session: RwLock::new(session),
|
||||||
request_count: RwLock::new(0),
|
request_count: RwLock::new(0),
|
||||||
open_requests: RwLock::new(HashMap::new()),
|
open_requests: RwLock::new(HashMap::new()),
|
||||||
pending_terminal_request: RwLock::new(false),
|
|
||||||
bans: RwLock::new(HashSet::new()),
|
bans: RwLock::new(HashSet::new()),
|
||||||
server: RwLock::new(server),
|
server: RwLock::new(server),
|
||||||
pool,
|
pool,
|
||||||
@ -61,23 +59,15 @@ impl AppState {
|
|||||||
pub async fn update_config(&self, new_config: AppConfig) -> Result<(), SetupError> {
|
pub async fn update_config(&self, new_config: AppConfig) -> Result<(), SetupError> {
|
||||||
let mut live_config = self.config.write().await;
|
let mut live_config = self.config.write().await;
|
||||||
|
|
||||||
// update autostart if necessary
|
|
||||||
if new_config.start_on_login != live_config.start_on_login {
|
if new_config.start_on_login != live_config.start_on_login {
|
||||||
config::set_auto_launch(new_config.start_on_login)?;
|
config::set_auto_launch(new_config.start_on_login)?;
|
||||||
}
|
}
|
||||||
// rebind socket if necessary
|
|
||||||
if new_config.listen_addr != live_config.listen_addr
|
if new_config.listen_addr != live_config.listen_addr
|
||||||
|| new_config.listen_port != live_config.listen_port
|
|| new_config.listen_port != live_config.listen_port
|
||||||
{
|
{
|
||||||
let mut sv = self.server.write().await;
|
let mut sv = self.server.write().await;
|
||||||
sv.rebind(new_config.listen_addr, new_config.listen_port).await?;
|
sv.rebind(new_config.listen_addr, new_config.listen_port).await?;
|
||||||
}
|
}
|
||||||
// re-register hotkeys if necessary
|
|
||||||
if new_config.hotkeys.show_window != live_config.hotkeys.show_window
|
|
||||||
|| new_config.hotkeys.launch_terminal != live_config.hotkeys.launch_terminal
|
|
||||||
{
|
|
||||||
config::register_hotkeys(&new_config.hotkeys)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
new_config.save(&self.pool).await?;
|
new_config.save(&self.pool).await?;
|
||||||
*live_config = new_config;
|
*live_config = new_config;
|
||||||
@ -151,21 +141,22 @@ impl AppState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn is_unlocked(&self) -> bool {
|
|
||||||
let session = self.session.read().await;
|
|
||||||
matches!(*session, Session::Unlocked{..})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn serialize_base_creds(&self) -> Result<String, GetCredentialsError> {
|
pub async fn serialize_base_creds(&self) -> Result<String, GetCredentialsError> {
|
||||||
let app_session = self.session.read().await;
|
let session = self.session.read().await;
|
||||||
let (base, _session) = app_session.try_get()?;
|
match *session {
|
||||||
Ok(serde_json::to_string(base).unwrap())
|
Session::Unlocked{ref base, ..} => Ok(serde_json::to_string(base).unwrap()),
|
||||||
|
Session::Locked(_) => Err(GetCredentialsError::Locked),
|
||||||
|
Session::Empty => Err(GetCredentialsError::Empty),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn serialize_session_creds(&self) -> Result<String, GetCredentialsError> {
|
pub async fn serialize_session_creds(&self) -> Result<String, GetCredentialsError> {
|
||||||
let app_session = self.session.read().await;
|
let session = self.session.read().await;
|
||||||
let (_bsae, session) = app_session.try_get()?;
|
match *session {
|
||||||
Ok(serde_json::to_string(session).unwrap())
|
Session::Unlocked{ref session, ..} => Ok(serde_json::to_string(session).unwrap()),
|
||||||
|
Session::Locked(_) => Err(GetCredentialsError::Locked),
|
||||||
|
Session::Empty => Err(GetCredentialsError::Empty),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn new_session(&self, base: BaseCredentials) -> Result<(), GetSessionError> {
|
async fn new_session(&self, base: BaseCredentials) -> Result<(), GetSessionError> {
|
||||||
@ -174,21 +165,4 @@ impl AppState {
|
|||||||
*app_session = Session::Unlocked {base, session};
|
*app_session = Session::Unlocked {base, session};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn register_terminal_request(&self) -> Result<(), ()> {
|
|
||||||
let mut req = self.pending_terminal_request.write().await;
|
|
||||||
if *req {
|
|
||||||
// if a request is already pending, we can't register a new one
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
*req = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn unregister_terminal_request(&self) {
|
|
||||||
let mut req = self.pending_terminal_request.write().await;
|
|
||||||
*req = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use tauri::Manager;
|
|
||||||
|
|
||||||
use crate::app::APP;
|
|
||||||
use crate::errors::*;
|
|
||||||
use crate::state::AppState;
|
|
||||||
|
|
||||||
|
|
||||||
pub async fn launch(use_base: bool) -> Result<(), LaunchTerminalError> {
|
|
||||||
let app = APP.get().unwrap();
|
|
||||||
let state = app.state::<AppState>();
|
|
||||||
|
|
||||||
// register_terminal_request() returns Err if there is another request pending
|
|
||||||
if state.register_terminal_request().await.is_err() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut cmd = {
|
|
||||||
let config = state.config.read().await;
|
|
||||||
let mut cmd = Command::new(&config.terminal.exec);
|
|
||||||
cmd.args(&config.terminal.args);
|
|
||||||
cmd
|
|
||||||
};
|
|
||||||
|
|
||||||
// if session is unlocked or empty, wait for credentials from frontend
|
|
||||||
if !state.is_unlocked().await {
|
|
||||||
app.emit_all("launch-terminal-request", ())?;
|
|
||||||
let window = app.get_window("main")
|
|
||||||
.ok_or(LaunchTerminalError::NoMainWindow)?;
|
|
||||||
if !window.is_visible()? {
|
|
||||||
window.unminimize()?;
|
|
||||||
window.show()?;
|
|
||||||
}
|
|
||||||
window.set_focus()?;
|
|
||||||
|
|
||||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
|
||||||
app.once_global("credentials-event", move |e| {
|
|
||||||
let success = match e.payload() {
|
|
||||||
Some("\"unlocked\"") | Some("\"entered\"") => true,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
let _ = tx.send(success);
|
|
||||||
});
|
|
||||||
|
|
||||||
if !rx.await.unwrap_or(false) {
|
|
||||||
state.unregister_terminal_request().await;
|
|
||||||
return Ok(()); // request was canceled by user
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// more lock-management
|
|
||||||
{
|
|
||||||
let app_session = state.session.read().await;
|
|
||||||
// session should really be unlocked at this point, but if the frontend misbehaves
|
|
||||||
// (i.e. lies about unlocking) we could end up here with a locked session
|
|
||||||
// this will result in an error popup to the user (see main hotkey handler)
|
|
||||||
let (base_creds, session_creds) = app_session.try_get()?;
|
|
||||||
if use_base {
|
|
||||||
cmd.env("AWS_ACCESS_KEY_ID", &base_creds.access_key_id);
|
|
||||||
cmd.env("AWS_SECRET_ACCESS_KEY", &base_creds.secret_access_key);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cmd.env("AWS_ACCESS_KEY_ID", &session_creds.access_key_id);
|
|
||||||
cmd.env("AWS_SECRET_ACCESS_KEY", &session_creds.secret_access_key);
|
|
||||||
cmd.env("AWS_SESSION_TOKEN", &session_creds.token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = match cmd.spawn() {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(e) if std::io::ErrorKind::NotFound == e.kind() => {
|
|
||||||
Err(ExecError::NotFound(cmd.get_program().to_owned()))
|
|
||||||
},
|
|
||||||
Err(e) => Err(ExecError::ExecutionFailed(e)),
|
|
||||||
};
|
|
||||||
|
|
||||||
state.unregister_terminal_request().await;
|
|
||||||
|
|
||||||
res?; // ? auto-conversion is more liberal than .into()
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -12,8 +12,7 @@
|
|||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
"os": {"all": true},
|
"os": {"all": true}
|
||||||
"dialog": {"open": true}
|
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
|
@ -16,20 +16,6 @@ listen('credentials-request', (tauriEvent) => {
|
|||||||
$appState.pendingRequests.put(tauriEvent.payload);
|
$appState.pendingRequests.put(tauriEvent.payload);
|
||||||
});
|
});
|
||||||
|
|
||||||
listen('launch-terminal-request', async (tauriEvent) => {
|
|
||||||
if ($appState.currentRequest === null) {
|
|
||||||
let status = await invoke('get_session_status');
|
|
||||||
if (status === 'locked') {
|
|
||||||
navigate('Unlock');
|
|
||||||
}
|
|
||||||
else if (status === 'empty') {
|
|
||||||
navigate('EnterCredentials');
|
|
||||||
}
|
|
||||||
// else, session is unlocked, so do nothing
|
|
||||||
// (although we shouldn't even get the event in that case)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
acceptRequest();
|
acceptRequest();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -9,10 +9,6 @@ export default function() {
|
|||||||
|
|
||||||
resolvers: [],
|
resolvers: [],
|
||||||
|
|
||||||
size() {
|
|
||||||
return this.items.length;
|
|
||||||
},
|
|
||||||
|
|
||||||
put(item) {
|
put(item) {
|
||||||
this.items.push(item);
|
this.items.push(item);
|
||||||
let resolver = this.resolvers.shift();
|
let resolver = this.resolvers.shift();
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
<script>
|
|
||||||
export let keys;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="flex gap-x-[0.2em] items-center">
|
|
||||||
{#each keys as key, i}
|
|
||||||
{#if i > 0}
|
|
||||||
<span class="mt-[-0.1em]">+</span>
|
|
||||||
{/if}
|
|
||||||
<kbd class="normal-case px-1 py-0.5 rounded border border-neutral">{key}</kbd>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
@ -1,27 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { open } from '@tauri-apps/api/dialog';
|
|
||||||
import Setting from './Setting.svelte';
|
|
||||||
|
|
||||||
export let title;
|
|
||||||
export let value;
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<Setting {title}>
|
|
||||||
<div slot="input">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="input input-sm input-bordered grow text-right"
|
|
||||||
bind:value
|
|
||||||
on:change={() => dispatch('update', {value})}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-primary"
|
|
||||||
on:click={async () => value = await open()}
|
|
||||||
>Browse</button>
|
|
||||||
</div>
|
|
||||||
<slot name="description" slot="description"></slot>
|
|
||||||
</Setting>
|
|
@ -1,61 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import KeyCombo from '../KeyCombo.svelte';
|
|
||||||
|
|
||||||
export let description;
|
|
||||||
export let value;
|
|
||||||
|
|
||||||
const id = Math.random().toString().slice(2);
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
let listening = false;
|
|
||||||
|
|
||||||
function listen() {
|
|
||||||
// don't re-listen if we already are
|
|
||||||
if (listening) return;
|
|
||||||
|
|
||||||
listening = true;
|
|
||||||
window.addEventListener('keyup', setKeybind, {once: true});
|
|
||||||
// setTimeout avoids reacting to the click event that we are currently processing
|
|
||||||
setTimeout(() => window.addEventListener('click', cancel, {once: true}), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setKeybind(event) {
|
|
||||||
console.log(event);
|
|
||||||
let keys = [];
|
|
||||||
if (event.ctrlKey) keys.push('ctrl');
|
|
||||||
if (event.altKey) keys.push('alt');
|
|
||||||
if (event.metaKey) keys.push('meta');
|
|
||||||
if (event.shiftKey) keys.push('shift');
|
|
||||||
keys.push(event.key);
|
|
||||||
|
|
||||||
value.keys = keys.join('+');
|
|
||||||
dispatch('update', {value});
|
|
||||||
listening = false;
|
|
||||||
window.removeEventListener('click', cancel, {once: true});
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancel() {
|
|
||||||
listening = false;
|
|
||||||
window.removeEventListener('keyup', setKeybind, {once: true});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<input
|
|
||||||
{id}
|
|
||||||
type="checkbox"
|
|
||||||
class="checkbox checkbox-primary"
|
|
||||||
bind:checked={value.enabled}
|
|
||||||
on:change={() => dispatch('update', {value})}
|
|
||||||
>
|
|
||||||
<label for={id} class="cursor-pointer ml-4 text-lg">{description}</label>
|
|
||||||
|
|
||||||
<button class="h-12 p-2 rounded border border-neutral cursor-pointer text-center" on:click={listen}>
|
|
||||||
{#if listening}
|
|
||||||
Click to cancel
|
|
||||||
{:else}
|
|
||||||
<KeyCombo keys={value.keys.split('+')} />
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
export let title;
|
export let title;
|
||||||
export let value;
|
export let value;
|
||||||
|
|
||||||
export let unit = '';
|
export let unit = '';
|
||||||
export let min = null;
|
export let min = null;
|
||||||
export let max = null;
|
export let max = null;
|
||||||
|
@ -6,12 +6,10 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div class="divider"></div>
|
||||||
<div class="flex flex-wrap justify-between gap-y-4">
|
<div class="flex justify-between">
|
||||||
<h3 class="text-lg font-bold shrink-0">{title}</h3>
|
<h3 class="text-lg font-bold">{title}</h3>
|
||||||
{#if $$slots.input}
|
|
||||||
<slot name="input"></slot>
|
<slot name="input"></slot>
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $$slots.description}
|
{#if $$slots.description}
|
||||||
@ -19,4 +17,3 @@
|
|||||||
<slot name="description"></slot>
|
<slot name="description"></slot>
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
<script>
|
|
||||||
export let name;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="divider mt-0 mb-8">
|
|
||||||
<h2 class="text-xl font-bold">{name}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-12">
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,22 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import Setting from './Setting.svelte';
|
|
||||||
|
|
||||||
export let title;
|
|
||||||
export let value;
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<Setting {title}>
|
|
||||||
<div slot="input">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="input input-sm input-bordered grow text-right"
|
|
||||||
bind:value
|
|
||||||
on:change={() => dispatch('update', {value})}
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<slot name="description" slot="description"></slot>
|
|
||||||
</Setting>
|
|
@ -1,5 +1,3 @@
|
|||||||
export { default as Setting } from './Setting.svelte';
|
export { default as Setting } from './Setting.svelte';
|
||||||
export { default as ToggleSetting } from './ToggleSetting.svelte';
|
export { default as ToggleSetting } from './ToggleSetting.svelte';
|
||||||
export { default as NumericSetting } from './NumericSetting.svelte';
|
export { default as NumericSetting } from './NumericSetting.svelte';
|
||||||
export { default as FileSetting } from './FileSetting.svelte';
|
|
||||||
export { default as TextSetting } from './TextSetting.svelte';
|
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
import { appState, completeRequest } from '../lib/state.js';
|
import { appState, completeRequest } from '../lib/state.js';
|
||||||
import ErrorAlert from '../ui/ErrorAlert.svelte';
|
import ErrorAlert from '../ui/ErrorAlert.svelte';
|
||||||
import Link from '../ui/Link.svelte';
|
import Link from '../ui/Link.svelte';
|
||||||
import KeyCombo from '../ui/KeyCombo.svelte';
|
|
||||||
|
|
||||||
|
|
||||||
// Send response to backend, display error if applicable
|
// Send response to backend, display error if applicable
|
||||||
@ -109,15 +108,17 @@
|
|||||||
<div class="w-full flex justify-between">
|
<div class="w-full flex justify-between">
|
||||||
<Link target={deny} hotkey="Escape">
|
<Link target={deny} hotkey="Escape">
|
||||||
<button class="btn btn-error justify-self-start">
|
<button class="btn btn-error justify-self-start">
|
||||||
<span class="mr-2">Deny</span>
|
Deny
|
||||||
<KeyCombo keys={['Esc']} />
|
<kbd class="ml-2 normal-case px-1 py-0.5 rounded border border-neutral">Esc</kbd>
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link target={approve} hotkey="Enter" shift="{true}">
|
<Link target={approve} hotkey="Enter" shift="{true}">
|
||||||
<button class="btn btn-success justify-self-end">
|
<button class="btn btn-success justify-self-end">
|
||||||
<span class="mr-2">Approve</span>
|
Approve
|
||||||
<KeyCombo keys={['Shift', 'Enter']} />
|
<kbd class="ml-2 normal-case px-1 py-0.5 rounded border border-neutral">Shift</kbd>
|
||||||
|
<span class="mx-0.5">+</span>
|
||||||
|
<kbd class="normal-case px-1 py-0.5 rounded border border-neutral">Enter</kbd>
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,7 +31,6 @@
|
|||||||
try {
|
try {
|
||||||
saving = true;
|
saving = true;
|
||||||
await invoke('save_credentials', {credentials, passphrase});
|
await invoke('save_credentials', {credentials, passphrase});
|
||||||
emit('credentials-event', 'entered');
|
|
||||||
if ($appState.currentRequest) {
|
if ($appState.currentRequest) {
|
||||||
navigate('Approve');
|
navigate('Approve');
|
||||||
}
|
}
|
||||||
@ -57,11 +56,6 @@
|
|||||||
saving = false;
|
saving = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancel() {
|
|
||||||
emit('credentials-event', 'enter-canceled');
|
|
||||||
navigate('Home');
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
@ -85,7 +79,7 @@
|
|||||||
Submit
|
Submit
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
<Link target={cancel} hotkey="Escape">
|
<Link target="Home" hotkey="Escape">
|
||||||
<button class="btn btn-sm btn-outline w-full">Cancel</button>
|
<button class="btn btn-sm btn-outline w-full">Cancel</button>
|
||||||
</Link>
|
</Link>
|
||||||
</form>
|
</form>
|
||||||
|
@ -10,11 +10,13 @@
|
|||||||
|
|
||||||
import vaultDoorSvg from '../assets/vault_door.svg?raw';
|
import vaultDoorSvg from '../assets/vault_door.svg?raw';
|
||||||
|
|
||||||
let launchBase = false;
|
|
||||||
function launchTerminal() {
|
// onMount(async () => {
|
||||||
invoke('launch_terminal', {base: launchBase});
|
// // will block until a request comes in
|
||||||
launchBase = false;
|
// let req = await $appState.pendingRequests.get();
|
||||||
}
|
// $appState.currentRequest = req;
|
||||||
|
// navigate('Approve');
|
||||||
|
// });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
@ -23,27 +25,21 @@
|
|||||||
</Nav>
|
</Nav>
|
||||||
|
|
||||||
<div class="flex flex-col h-screen items-center justify-center p-4 space-y-4">
|
<div class="flex flex-col h-screen items-center justify-center p-4 space-y-4">
|
||||||
<div class="flex flex-col items-center space-y-4">
|
|
||||||
{@html vaultDoorSvg}
|
|
||||||
{#await invoke('get_session_status') then status}
|
{#await invoke('get_session_status') then status}
|
||||||
{#if status === 'locked'}
|
{#if status === 'locked'}
|
||||||
|
|
||||||
|
{@html vaultDoorSvg}
|
||||||
<h2 class="text-2xl font-bold">Creddy is locked</h2>
|
<h2 class="text-2xl font-bold">Creddy is locked</h2>
|
||||||
<Link target="Unlock" hotkey="Enter" class="w-64">
|
<Link target="Unlock" hotkey="Enter" class="w-64">
|
||||||
<button class="btn btn-primary w-full">Unlock</button>
|
<button class="btn btn-primary w-full">Unlock</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{:else if status === 'unlocked'}
|
{:else if status === 'unlocked'}
|
||||||
|
{@html vaultDoorSvg}
|
||||||
<h2 class="text-2xl font-bold">Waiting for requests</h2>
|
<h2 class="text-2xl font-bold">Waiting for requests</h2>
|
||||||
<button class="btn btn-primary w-full" on:click={launchTerminal}>
|
|
||||||
Launch Terminal
|
|
||||||
</button>
|
|
||||||
<label class="label cursor-pointer flex items-center space-x-2">
|
|
||||||
<input type="checkbox" class="checkbox checkbox-sm" bind:checked={launchBase}>
|
|
||||||
<span class="label-text">Launch with base credentials</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{:else if status === 'empty'}
|
{:else if status === 'empty'}
|
||||||
|
{@html vaultDoorSvg}
|
||||||
<h2 class="text-2xl font-bold">No credentials found</h2>
|
<h2 class="text-2xl font-bold">No credentials found</h2>
|
||||||
<Link target="EnterCredentials" hotkey="Enter" class="w-64">
|
<Link target="EnterCredentials" hotkey="Enter" class="w-64">
|
||||||
<button class="btn btn-primary w-full">Enter Credentials</button>
|
<button class="btn btn-primary w-full">Enter Credentials</button>
|
||||||
@ -51,4 +47,3 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
@ -1,19 +1,12 @@
|
|||||||
<script context="module">
|
|
||||||
import { type } from '@tauri-apps/api/os';
|
|
||||||
const osType = await type();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { invoke } from '@tauri-apps/api/tauri';
|
import { invoke } from '@tauri-apps/api/tauri';
|
||||||
|
import { type } from '@tauri-apps/api/os';
|
||||||
|
|
||||||
import { appState } from '../lib/state.js';
|
import { appState } from '../lib/state.js';
|
||||||
import Nav from '../ui/Nav.svelte';
|
import Nav from '../ui/Nav.svelte';
|
||||||
import Link from '../ui/Link.svelte';
|
import Link from '../ui/Link.svelte';
|
||||||
import ErrorAlert from '../ui/ErrorAlert.svelte';
|
import ErrorAlert from '../ui/ErrorAlert.svelte';
|
||||||
import SettingsGroup from '../ui/settings/SettingsGroup.svelte';
|
import { Setting, ToggleSetting, NumericSetting } from '../ui/settings';
|
||||||
import Keybind from '../ui/settings/Keybind.svelte';
|
|
||||||
import { Setting, ToggleSetting, NumericSetting, FileSetting, TextSetting } from '../ui/settings';
|
|
||||||
|
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import { backInOut } from 'svelte/easing';
|
import { backInOut } from 'svelte/easing';
|
||||||
@ -21,7 +14,6 @@
|
|||||||
|
|
||||||
let error = null;
|
let error = null;
|
||||||
async function save() {
|
async function save() {
|
||||||
console.log('updating config');
|
|
||||||
try {
|
try {
|
||||||
await invoke('save_config', {config: $appState.config});
|
await invoke('save_config', {config: $appState.config});
|
||||||
}
|
}
|
||||||
@ -30,16 +22,20 @@
|
|||||||
$appState.config = await invoke('get_config');
|
$appState.config = await invoke('get_config');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let osType = '';
|
||||||
|
type().then(t => osType = t);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<Nav>
|
<Nav>
|
||||||
<h1 slot="title" class="text-2xl font-bold">Settings</h1>
|
<h2 slot="title" class="text-2xl font-bold">Settings</h2>
|
||||||
</Nav>
|
</Nav>
|
||||||
|
|
||||||
{#await invoke('get_config') then config}
|
{#await invoke('get_config') then config}
|
||||||
<div class="max-w-lg mx-auto mt-1.5 p-4 space-y-16">
|
<div class="max-w-md mx-auto mt-1.5 p-4">
|
||||||
<SettingsGroup name="General">
|
<!-- <h2 class="text-2xl font-bold text-center">Settings</h2> -->
|
||||||
|
|
||||||
<ToggleSetting title="Start on login" bind:value={$appState.config.start_on_login} on:update={save}>
|
<ToggleSetting title="Start on login" bind:value={$appState.config.start_on_login} on:update={save}>
|
||||||
<svelte:fragment slot="description">
|
<svelte:fragment slot="description">
|
||||||
Start Creddy when you log in to your computer.
|
Start Creddy when you log in to your computer.
|
||||||
@ -80,29 +76,6 @@
|
|||||||
Update or re-enter your encrypted credentials.
|
Update or re-enter your encrypted credentials.
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</Setting>
|
</Setting>
|
||||||
|
|
||||||
<FileSetting
|
|
||||||
title="Terminal emulator"
|
|
||||||
bind:value={$appState.config.terminal.exec}
|
|
||||||
on:update={save}
|
|
||||||
>
|
|
||||||
<svelte:fragment slot="description">
|
|
||||||
Choose your preferred terminal emulator (e.g. <code>gnome-terminal</code> or <code>wt.exe</code>.) May be an absolute path or an executable discoverable on <code>$PATH</code>.
|
|
||||||
</svelte:fragment>
|
|
||||||
</FileSetting>
|
|
||||||
</SettingsGroup>
|
|
||||||
|
|
||||||
<SettingsGroup name="Hotkeys">
|
|
||||||
<div class="space-y-4">
|
|
||||||
<p>Click on a keybinding to modify it. Use the checkbox to enable or disable a keybinding entirely.</p>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-[auto_1fr_auto] gap-y-3 items-center">
|
|
||||||
<Keybind description="Show Creddy" value={$appState.config.hotkeys.show_window} on:update={save} />
|
|
||||||
<Keybind description="Launch terminal" value={$appState.config.hotkeys.launch_terminal} on:update={save} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SettingsGroup>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { invoke } from '@tauri-apps/api/tauri';
|
import { invoke } from '@tauri-apps/api/tauri';
|
||||||
import { emit } from '@tauri-apps/api/event';
|
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
import { appState } from '../lib/state.js';
|
import { appState } from '../lib/state.js';
|
||||||
@ -27,7 +26,6 @@
|
|||||||
saving = true;
|
saving = true;
|
||||||
let r = await invoke('unlock', {passphrase});
|
let r = await invoke('unlock', {passphrase});
|
||||||
$appState.credentialStatus = 'unlocked';
|
$appState.credentialStatus = 'unlocked';
|
||||||
emit('credentials-event', 'unlocked');
|
|
||||||
if ($appState.currentRequest) {
|
if ($appState.currentRequest) {
|
||||||
navigate('Approve');
|
navigate('Approve');
|
||||||
}
|
}
|
||||||
@ -53,11 +51,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancel() {
|
|
||||||
emit('credentials-event', 'unlock-canceled');
|
|
||||||
navigate('Home');
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
loadTime = Date.now();
|
loadTime = Date.now();
|
||||||
})
|
})
|
||||||
@ -82,7 +75,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Link target={cancel} hotkey="Escape">
|
<Link target="Home" hotkey="Escape">
|
||||||
<button class="btn btn-sm btn-outline w-full">Cancel</button>
|
<button class="btn btn-outline btn-sm w-full">Cancel</button>
|
||||||
</Link>
|
</Link>
|
||||||
</form>
|
</form>
|
||||||
|
6
todo.md
6
todo.md
@ -1,6 +0,0 @@
|
|||||||
* Switch to "process" provider for AWS credentials (much less hacky)
|
|
||||||
* Session timeout (plain duration, or activity-based?)
|
|
||||||
* Fix rehide behavior when new request comes in while old one is still being resolved
|
|
||||||
* Additional hotkey configuration (approve/deny at the very least)
|
|
||||||
* Logging
|
|
||||||
* SSH key handling
|
|
Loading…
x
Reference in New Issue
Block a user