add ui for idle timeout

This commit is contained in:
2024-02-06 20:27:51 -08:00
parent 141334f7e2
commit 87617a0726
9 changed files with 155 additions and 32 deletions

View File

@ -2,6 +2,7 @@
import { onMount } from 'svelte';
import { listen } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/tauri';
import { getVersion } from '@tauri-apps/api/app';
import { appState, acceptRequest, cleanupRequest } from './lib/state.js';
import { views, currentView, navigate } from './lib/routing.js';
@ -11,6 +12,8 @@ $views = import.meta.glob('./views/*.svelte', {eager: true});
navigate('Home');
invoke('get_config').then(config => $appState.config = config);
invoke('get_session_status').then(status => $appState.credentialStatus = status);
getVersion().then(version => $appState.appVersion = version);
listen('credentials-request', (tauriEvent) => {
$appState.pendingRequests.put(tauriEvent.payload);
@ -40,6 +43,10 @@ listen('launch-terminal-request', async (tauriEvent) => {
}
});
listen('locked', () => {
$appState.credentialStatus = 'locked';
});
invoke('get_setup_errors')
.then(errs => {
$appState.setupErrors = errs.map(e => ({msg: e, show: true}));
@ -49,4 +56,9 @@ acceptRequest();
</script>
<svelte:window
on:click={() => invoke('signal_activity')}
on:keydown={() => invoke('signal_activity')}
/>
<svelte:component this="{$currentView}" />

View File

@ -9,6 +9,7 @@ export let appState = writable({
pendingRequests: queue(),
credentialStatus: 'locked',
setupErrors: [],
appVersion: '',
});

View File

@ -1,6 +1,6 @@
<script>
export let keys;
let classes;
let classes = '';
export {classes as class};
</script>

View File

@ -0,0 +1,92 @@
<script>
import Setting from './Setting.svelte';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
export let title;
// seconds are required
export let seconds;
export let min = 0;
export let max = null;
// best unit is the unit that results in the smallest non-fractional number
let unit = null;
const UNITS = {
Seconds: 1,
Minutes: 60,
Hours: 3600,
Days: 86400,
};
if (unit === null) {
let min = Infinity;
let bestUnit = null;
for (const [u, multiplier] of Object.entries(UNITS)) {
const v = seconds / multiplier;
if (v < min && v >= 1) {
min = v;
bestUnit = u;
}
}
unit = bestUnit;
}
// local value is only one-way synced to value so that we can better handle changes
$: localValue = (seconds / UNITS[unit]).toString();
let error = null;
function updateValue() {
localValue = localValue.replace(/[^0-9.]/g, '');
// Don't update the value, but also don't error, if it's empty,
// or if it could be the start of a float
if (localValue === '' || localValue === '.') {
error = null;
return;
}
const num = parseFloat(localValue);
if (num < 0) {
error = `${num} is not a valid duration`
}
else if (min !== null && num < min) {
error = `Too low (minimum ${min * UNITS[unit]}`;
}
else if (max !== null & num > max) {
error = `Too high (maximum ${max * UNITS[unit]}`;
}
else {
error = null;
seconds = Math.round(num * UNITS[unit]);
dispatch('update', {seconds});
}
}
</script>
<Setting {title}>
<div slot="input">
<select class="select select-bordered select-sm mr-2" bind:value={unit}>
{#each Object.keys(UNITS) as u}
<option selected={u === unit || null}>{u}</option>
{/each}
</select>
<div class="tooltip tooltip-error" class:tooltip-open={error !== null} data-tip={error}>
<input
type="text"
class="input input-sm input-bordered text-right"
size={Math.max(5, localValue.length)}
class:input-error={error}
bind:value={localValue}
on:input={updateValue}
>
</div>
</div>
<slot name="description" slot="description"></slot>
</Setting>

View File

@ -3,3 +3,4 @@ export { default as ToggleSetting } from './ToggleSetting.svelte';
export { default as NumericSetting } from './NumericSetting.svelte';
export { default as FileSetting } from './FileSetting.svelte';
export { default as TextSetting } from './TextSetting.svelte';
export { default as TimeSetting } from './TimeSetting.svelte';

View File

@ -25,31 +25,29 @@
<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}
{#if status === 'locked'}
{#if $appState.credentialStatus === 'locked'}
<h2 class="text-2xl font-bold">Creddy is locked</h2>
<Link target="Unlock" hotkey="Enter" class="w-64">
<button class="btn btn-primary w-full">Unlock</button>
</Link>
<h2 class="text-2xl font-bold">Creddy is locked</h2>
<Link target="Unlock" hotkey="Enter" class="w-64">
<button class="btn btn-primary w-full">Unlock</button>
</Link>
{:else if status === 'unlocked'}
<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">
<span class="label-text">Launch with long-lived credentials</span>
<input type="checkbox" class="checkbox checkbox-sm" bind:checked={launchBase}>
</label>
{:else if $appState.credentialStatus === 'unlocked'}
<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">
<span class="label-text">Launch with long-lived credentials</span>
<input type="checkbox" class="checkbox checkbox-sm" bind:checked={launchBase}>
</label>
{:else if status === 'empty'}
<h2 class="text-2xl font-bold">No credentials found</h2>
<Link target="EnterCredentials" hotkey="Enter" class="w-64">
<button class="btn btn-primary w-full">Enter Credentials</button>
</Link>
{/if}
{/await}
{:else if $appState.credentialStatus === 'empty'}
<h2 class="text-2xl font-bold">No credentials found</h2>
<Link target="EnterCredentials" hotkey="Enter" class="w-64">
<button class="btn btn-primary w-full">Enter Credentials</button>
</Link>
{/if}
</div>
</div>

View File

@ -8,7 +8,7 @@
import ErrorAlert from '../ui/ErrorAlert.svelte';
import SettingsGroup from '../ui/settings/SettingsGroup.svelte';
import Keybind from '../ui/settings/Keybind.svelte';
import { Setting, ToggleSetting, NumericSetting, FileSetting, TextSetting } from '../ui/settings';
import { Setting, ToggleSetting, NumericSetting, FileSetting, TextSetting, TimeSetting } from '../ui/settings';
import { fly } from 'svelte/transition';
import { backInOut } from 'svelte/easing';
@ -38,7 +38,7 @@
<h1 slot="title" class="text-2xl font-bold">Settings</h1>
</Nav>
<div class="max-w-lg mx-auto mt-1.5 mb-24 p-4 space-y-16">
<div class="max-w-lg mx-auto my-1.5 p-4 space-y-16">
<SettingsGroup name="General">
<ToggleSetting title="Start on login" bind:value={config.start_on_login}>
<svelte:fragment slot="description">
@ -60,6 +60,20 @@
</svelte:fragment>
</NumericSetting>
<ToggleSetting title="Lock when idle" bind:value={config.auto_lock}>
<svelte:fragment slot="description">
Automatically lock Creddy after a period of inactivity.
</svelte:fragment>
</ToggleSetting>
{#if config.auto_lock}
<TimeSetting title="Idle timeout" bind:seconds={config.lock_after.secs}>
<svelte:fragment slot="description">
How long to wait before automatically locking.
</svelte:fragment>
</TimeSetting>
{/if}
<Setting title="Update credentials">
<Link slot="input" target="EnterCredentials">
<button class="btn btn-sm btn-primary">Update</button>
@ -91,6 +105,9 @@
</div>
</SettingsGroup>
<p class="text-sm text-right">
Creddy {$appState.appVersion}
</p>
</div>
{#if error}