mirror of
https://github.com/librekeys/picoforge.git
synced 2026-07-28 08:01:19 -07:00
Display error instead of when there was an error communicating with pcscd
This commit is contained in:
+7
-7
@@ -46,14 +46,14 @@ pub fn reboot(to_bootsel: bool) -> Result<String, PFError> {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_credentials(pin: String) -> Result<Vec<StoredCredential>, String> {
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
fido::get_credentials(pin)
|
||||
}).await.map_err(|e| e.to_string())?
|
||||
tauri::async_runtime::spawn_blocking(move || fido::get_credentials(pin))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_credential(pin: String, credential_id: String) -> Result<String, String> {
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
fido::delete_credential(pin, credential_id)
|
||||
}).await.map_err(|e| e.to_string())?
|
||||
}
|
||||
tauri::async_runtime::spawn_blocking(move || fido::delete_credential(pin, credential_id))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
}
|
||||
|
||||
@@ -12,7 +12,10 @@ use std::io::Cursor;
|
||||
|
||||
/// Connects to the first available reader and selects the Rescue Applet
|
||||
fn connect_and_select() -> Result<(pcsc::Card, Vec<u8>), PFError> {
|
||||
let ctx = Context::establish(Scope::User)?;
|
||||
let ctx = Context::establish(Scope::User).map_err(|e| {
|
||||
log::error!("Failed to establish PCSC context: {}", e);
|
||||
PFError::Pcsc(e)
|
||||
})?;
|
||||
|
||||
let mut readers_buf = [0; 2048];
|
||||
let mut readers = ctx.list_readers(&mut readers_buf)?;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import * as Alert from "$lib/components/ui/alert";
|
||||
import { TriangleAlert } from "@lucide/svelte";
|
||||
import { device } from "$lib/device/manager.svelte";
|
||||
|
||||
interface Props {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
message = "Please connect your device and click Refresh to begin.",
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<Alert.Root variant={device.error ? "destructive" : "default"}>
|
||||
<TriangleAlert class="h-4 w-4" />
|
||||
<Alert.Title
|
||||
>{device.error ? "Connection Error" : "No Device Connected"}</Alert.Title
|
||||
>
|
||||
<Alert.Description>
|
||||
{device.error || message}
|
||||
</Alert.Description>
|
||||
</Alert.Root>
|
||||
@@ -15,6 +15,7 @@ class DeviceManager {
|
||||
loading = $state(false);
|
||||
connected = $state(false);
|
||||
fidoInfo: FidoInfo | null = $state(null);
|
||||
error: string | null = $state(null);
|
||||
|
||||
credentials: StoredCredential[] = $state([]);
|
||||
unlocked = $state(false);
|
||||
@@ -38,6 +39,7 @@ class DeviceManager {
|
||||
|
||||
async refresh() {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
try {
|
||||
logger.add("Attempting to connect to device...", "info");
|
||||
|
||||
@@ -66,10 +68,13 @@ class DeviceManager {
|
||||
logger.add(`Device Connected! Serial: ${this.info.serial}, FW: v${this.info.firmwareVersion}`, "success");
|
||||
}
|
||||
this.connected = true;
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
console.error("Connection failed:", err);
|
||||
this.error = err;
|
||||
if (this.connected) {
|
||||
logger.add(`Connection lost: ${err}`, "error");
|
||||
} else {
|
||||
logger.add(`Connection failed: ${err}`, "error");
|
||||
}
|
||||
this.connected = false;
|
||||
} finally {
|
||||
|
||||
@@ -2,7 +2,19 @@
|
||||
import { onMount } from "svelte";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
|
||||
import { Home, Info, KeyRound, Maximize, Minimize, Minus, RefreshCw, ScrollText, Settings, ShieldCheck, X } from "@lucide/svelte";
|
||||
import {
|
||||
Home,
|
||||
Info,
|
||||
KeyRound,
|
||||
Maximize,
|
||||
Minimize,
|
||||
Minus,
|
||||
RefreshCw,
|
||||
ScrollText,
|
||||
Settings,
|
||||
ShieldCheck,
|
||||
X,
|
||||
} from "@lucide/svelte";
|
||||
import type { Component } from "svelte";
|
||||
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
@@ -52,10 +64,10 @@
|
||||
isMaximized = maximized;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
handleResize();
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
@@ -66,8 +78,15 @@
|
||||
<Sidebar.Root collapsible="icon">
|
||||
<Sidebar.Header>
|
||||
<div class="flex items-center gap-3 p-2">
|
||||
<img src="/in.suyogtandel.picoforge.svg" alt="PicoForge Logo" class="h-12 w-12 shadow-sm" />
|
||||
<span class="font-bold text-xl tracking-tight group-data-[collapsible=icon]:hidden">PicoForge</span>
|
||||
<img
|
||||
src="/in.suyogtandel.picoforge.svg"
|
||||
alt="PicoForge Logo"
|
||||
class="h-12 w-12 shadow-sm"
|
||||
/>
|
||||
<span
|
||||
class="font-bold text-xl tracking-tight group-data-[collapsible=icon]:hidden"
|
||||
>PicoForge</span
|
||||
>
|
||||
</div>
|
||||
</Sidebar.Header>
|
||||
|
||||
@@ -78,7 +97,10 @@
|
||||
<Sidebar.Menu>
|
||||
{#each menuItems as item}
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={currentView === item.view} onclick={() => onViewChange(item.view)}>
|
||||
<Sidebar.MenuButton
|
||||
isActive={currentView === item.view}
|
||||
onclick={() => onViewChange(item.view)}
|
||||
>
|
||||
<item.icon />
|
||||
<span>{item.label}</span>
|
||||
</Sidebar.MenuButton>
|
||||
@@ -89,18 +111,40 @@
|
||||
</Sidebar.Group>
|
||||
</Sidebar.Content>
|
||||
|
||||
<Sidebar.Footer class="border-t bg-background/50 p-2 group-data-[collapsible=icon]:p-2">
|
||||
<Sidebar.Footer
|
||||
class="border-t bg-background/50 p-2 group-data-[collapsible=icon]:p-2"
|
||||
>
|
||||
<div class="p-2 space-y-3 group-data-[collapsible=icon]:hidden">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs font-medium text-muted-foreground">Device Status</span>
|
||||
<span class="text-xs font-medium text-muted-foreground"
|
||||
>Device Status</span
|
||||
>
|
||||
{#if device.connected}
|
||||
<Badge variant="default" class="bg-green-600 hover:bg-green-600 text-[10px] px-1.5 h-5">Online</Badge>
|
||||
<Badge
|
||||
variant="default"
|
||||
class="bg-green-600 hover:bg-green-600 text-[10px] px-1.5 h-5"
|
||||
>Online</Badge
|
||||
>
|
||||
{:else if device.error}
|
||||
<Badge
|
||||
variant="destructive"
|
||||
class="bg-amber-600 hover:bg-amber-600 text-[10px] px-1.5 h-5"
|
||||
>Error</Badge
|
||||
>
|
||||
{:else}
|
||||
<Badge variant="destructive" class="text-[10px] px-1.5 h-5">Offline</Badge>
|
||||
<Badge variant="destructive" class="text-[10px] px-1.5 h-5"
|
||||
>Offline</Badge
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Button variant="outline" size="sm" class="w-full gap-2" disabled={device.loading} onclick={() => device.refresh()}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="w-full gap-2"
|
||||
disabled={device.loading}
|
||||
onclick={() => device.refresh()}
|
||||
>
|
||||
{#if device.loading}
|
||||
<RefreshCw class="h-3.5 w-3.5 animate-spin" />
|
||||
{:else}
|
||||
@@ -109,28 +153,52 @@
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
<div class="hidden group-data-[collapsible=icon]:flex flex-col items-center justify-center p-2 gap-2">
|
||||
<Button variant="ghost" size="icon" disabled={device.loading} onclick={() => device.refresh()}>
|
||||
<div
|
||||
class="hidden group-data-[collapsible=icon]:flex flex-col items-center justify-center p-2 gap-2"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
disabled={device.loading}
|
||||
onclick={() => device.refresh()}
|
||||
>
|
||||
<RefreshCw class="h-4 w-4 {device.loading ? 'animate-spin' : ''}" />
|
||||
</Button>
|
||||
<div class={`h-2 w-2 rounded-full ${device.connected ? "bg-green-500" : "bg-red-500"}`}></div>
|
||||
<div
|
||||
class={`h-2 w-2 rounded-full ${device.connected ? "bg-green-500" : device.error ? "bg-amber-500" : "bg-red-500"}`}
|
||||
></div>
|
||||
</div>
|
||||
</Sidebar.Footer>
|
||||
</Sidebar.Root>
|
||||
|
||||
<Sidebar.Inset>
|
||||
<header data-tauri-drag-region class="h-10 bg-background border-b flex items-center justify-between px-2 select-none sticky top-0 z-10">
|
||||
<header
|
||||
data-tauri-drag-region
|
||||
class="h-10 bg-background border-b flex items-center justify-between px-2 select-none sticky top-0 z-10"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Sidebar.Trigger class="h-8 w-8" />
|
||||
<div class="text-xs font-medium text-muted-foreground pointer-events-none flex items-center gap-2"></div>
|
||||
<div
|
||||
class="text-xs font-medium text-muted-foreground pointer-events-none flex items-center gap-2"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1">
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8 hover:bg-muted" onclick={minimize}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 hover:bg-muted"
|
||||
onclick={minimize}
|
||||
>
|
||||
<Minus class="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8 hover:bg-muted" onclick={toggleMaximize}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 hover:bg-muted"
|
||||
onclick={toggleMaximize}
|
||||
>
|
||||
{#if isMaximized}
|
||||
<Minimize class="h-3.5 w-3.5 rotate-180" />
|
||||
{:else}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import { Microchip, RefreshCw, Save, Settings, Tag, TriangleAlert, X, Key } from "@lucide/svelte";
|
||||
|
||||
import { configViewState as state } from "$lib/state/configState.svelte";
|
||||
import NoDeviceStatus from "$lib/components/device/NoDeviceStatus.svelte";
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
@@ -24,11 +25,7 @@
|
||||
</div>
|
||||
|
||||
{#if !device.connected}
|
||||
<Alert.Root>
|
||||
<TriangleAlert class="h-4 w-4" />
|
||||
<Alert.Title>No Device Connected</Alert.Title>
|
||||
<Alert.Description>Connect your device to access configuration options.</Alert.Description>
|
||||
</Alert.Root>
|
||||
<NoDeviceStatus message="Connect your device to access configuration options." />
|
||||
{:else}
|
||||
<div class="grid gap-6 lg:grid-cols-2">
|
||||
<Card.Root class="lg:col-span-2">
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { device } from "$lib/device/manager.svelte";
|
||||
|
||||
import { Cpu, Lock, LockOpen, Microchip, ShieldCheck, TriangleAlert, Shield } from "@lucide/svelte";
|
||||
import NoDeviceStatus from "$lib/components/device/NoDeviceStatus.svelte";
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
@@ -17,11 +18,7 @@
|
||||
</div>
|
||||
|
||||
{#if !device.connected}
|
||||
<Alert.Root>
|
||||
<TriangleAlert class="h-4 w-4" />
|
||||
<Alert.Title>No Device Connected</Alert.Title>
|
||||
<Alert.Description>Please connect your device and click Refresh to begin.</Alert.Description>
|
||||
</Alert.Root>
|
||||
<NoDeviceStatus />
|
||||
{:else}
|
||||
<div class="grid gap-6 md:grid-cols-2">
|
||||
<Card.Root>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import { TriangleAlert, KeyRound, Trash2, Lock, Unlock, Loader2, Shield } from "@lucide/svelte";
|
||||
import { device } from "$lib/device/manager.svelte";
|
||||
import type { StoredCredential } from "$lib/device/types.svelte";
|
||||
import NoDeviceStatus from "$lib/components/device/NoDeviceStatus.svelte";
|
||||
|
||||
let loading = $state(false);
|
||||
let pin = $state("");
|
||||
@@ -108,11 +109,7 @@
|
||||
</div>
|
||||
|
||||
{#if !device.connected}
|
||||
<Alert.Root>
|
||||
<TriangleAlert class="h-4 w-4" />
|
||||
<Alert.Title>No Device Connected</Alert.Title>
|
||||
<Alert.Description>Connect your pico-key to manage passkeys.</Alert.Description>
|
||||
</Alert.Root>
|
||||
<NoDeviceStatus message="Connect your pico-key to manage passkeys." />
|
||||
{:else}
|
||||
<Dialog.Root bind:open={showPinDialog}>
|
||||
<Dialog.Content class="sm:max-w-md">
|
||||
|
||||
Reference in New Issue
Block a user