add ui for idle timeout
This commit is contained in:
@ -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}" />
|
||||
|
@ -9,6 +9,7 @@ export let appState = writable({
|
||||
pendingRequests: queue(),
|
||||
credentialStatus: 'locked',
|
||||
setupErrors: [],
|
||||
appVersion: '',
|
||||
});
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script>
|
||||
export let keys;
|
||||
let classes;
|
||||
let classes = '';
|
||||
export {classes as class};
|
||||
</script>
|
||||
|
||||
|
92
src/ui/settings/TimeSetting.svelte
Normal file
92
src/ui/settings/TimeSetting.svelte
Normal 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>
|
@ -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';
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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}
|
||||
|
Reference in New Issue
Block a user