Harden settings: split breadcrumbs secrets, typed exec, new panels
Load and save Wi-Fi networks in networks.toml (0600) instead of writing PSKs back into breadcrumbs.toml. The password field is write-only. Replace the generic argv runner with typed bakery/pacman/fwupd commands and set a real Tauri CSP. Add Lock, Screenshots, Monitors, and Help panels. Pin bread-theme and bread-utils to bread-ecosystem v0.7.1. bread-screenshots is not on that tag, so --screenshot calls grim locally. bakery.toml lists webkitgtk-4.1 deps; README/CLAUDE.md match Tauri 2 + Svelte 5 and single-trunk main.
This commit is contained in:
parent
abfb4fd4a4
commit
cbac50683e
30 changed files with 1469 additions and 147 deletions
|
|
@ -3,7 +3,7 @@
|
|||
</script>
|
||||
|
||||
<div class="placeholder">
|
||||
<p>"{page}" hasn't been migrated to Tauri yet.</p>
|
||||
<p>Unknown page "{page}".</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ import Package from "@lucide/svelte/icons/package";
|
|||
import RefreshCw from "@lucide/svelte/icons/refresh-cw";
|
||||
import History from "@lucide/svelte/icons/history";
|
||||
import Info from "@lucide/svelte/icons/info";
|
||||
import Lock from "@lucide/svelte/icons/lock";
|
||||
import Camera from "@lucide/svelte/icons/camera";
|
||||
import AppWindow from "@lucide/svelte/icons/app-window";
|
||||
import CircleHelp from "@lucide/svelte/icons/circle-help";
|
||||
|
||||
export interface SidebarItem {
|
||||
/** Must match a key in the view component map (see routing in +page.svelte). */
|
||||
|
|
@ -47,7 +51,10 @@ export const SYSTEM_ITEMS: SidebarItem[] = [
|
|||
{ id: "power", label: "Power", icon: BatteryFull },
|
||||
{ id: "datetime", label: "Date & Time", icon: Clock },
|
||||
{ id: "hyprland", label: "Display", sublabel: "monitors.json", icon: Monitor },
|
||||
{ id: "breadmon", label: "Monitors", sublabel: "breadmon", icon: AppWindow },
|
||||
{ id: "breadlock", label: "Lock & greet", sublabel: "breadlock", icon: Lock },
|
||||
{ id: "keybinds", label: "Keybinds", sublabel: "binds.json", icon: Keyboard },
|
||||
{ id: "breadshot", label: "Screenshots", sublabel: "breadshot", icon: Camera },
|
||||
{ id: "autostart", label: "Startup Apps", sublabel: "autostart.json", icon: Rocket },
|
||||
{ id: "users", label: "Users", icon: Users },
|
||||
];
|
||||
|
|
@ -70,7 +77,10 @@ export const MAINTENANCE_ITEMS: SidebarItem[] = [
|
|||
{ id: "snapshots", label: "Snapshots", icon: History },
|
||||
];
|
||||
|
||||
export const ABOUT_ITEMS: SidebarItem[] = [{ id: "about", label: "About", icon: Info }];
|
||||
export const ABOUT_ITEMS: SidebarItem[] = [
|
||||
{ id: "breadhelp", label: "Help", sublabel: "breadhelp", icon: CircleHelp },
|
||||
{ id: "about", label: "About", icon: Info },
|
||||
];
|
||||
|
||||
export interface SidebarSection {
|
||||
title: string | null;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
// Frontend half of the event-streaming command pattern (see
|
||||
// src-tauri/src/commands/streaming.rs) — runs a command, appends each
|
||||
// stdout/stderr line to a reactive log as it arrives, and resolves once the
|
||||
// process exits with whether it succeeded.
|
||||
// src/src/commands/streaming.rs) — listens for `cmd-output` lines from a
|
||||
// typed Tauri command that runs a hardcoded program, then resolves once
|
||||
// the process exits.
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
export async function runStreamingCommand(
|
||||
program: string,
|
||||
args: string[],
|
||||
export async function runStreamed(
|
||||
command: string,
|
||||
args: Record<string, unknown>,
|
||||
onLine: (line: string) => void,
|
||||
): Promise<boolean> {
|
||||
const sessionId = crypto.randomUUID();
|
||||
|
|
@ -18,7 +18,7 @@ export async function runStreamingCommand(
|
|||
});
|
||||
|
||||
try {
|
||||
return await invoke<boolean>("run_streaming_command", { sessionId, program, args });
|
||||
return await invoke<boolean>(command, { sessionId, ...args });
|
||||
} finally {
|
||||
unlisten();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@
|
|||
}
|
||||
interface Network {
|
||||
ssid: string;
|
||||
password: string;
|
||||
// Write-only. The backend never returns a stored PSK; empty means
|
||||
// "keep the on-disk secret / let NetworkManager remember."
|
||||
password?: string | null;
|
||||
hidden: boolean;
|
||||
}
|
||||
interface Profile {
|
||||
|
|
@ -51,7 +53,9 @@
|
|||
let savedSsids = $derived(cfg?.networks.map((n) => n.ssid).filter((s) => s.trim().length > 0) ?? []);
|
||||
|
||||
onMount(async () => {
|
||||
cfg = await invoke<BreadcrumbsConfig>("get_breadcrumbs_config");
|
||||
const loaded = await invoke<BreadcrumbsConfig>("get_breadcrumbs_config");
|
||||
loaded.networks = loaded.networks.map((n) => ({ ...n, password: "" }));
|
||||
cfg = loaded;
|
||||
});
|
||||
|
||||
function addNetwork() {
|
||||
|
|
@ -71,7 +75,20 @@
|
|||
}
|
||||
|
||||
async function save() {
|
||||
await invoke("save_breadcrumbs_config", { input: cfg });
|
||||
const payload = {
|
||||
...cfg!,
|
||||
networks: cfg!.networks.map((n) => ({
|
||||
ssid: n.ssid,
|
||||
hidden: n.hidden,
|
||||
// Omit empty so the backend treats it as "keep / let NM remember"
|
||||
// rather than writing password = "".
|
||||
...(n.password && n.password.length > 0 ? { password: n.password } : {}),
|
||||
})),
|
||||
};
|
||||
await invoke("save_breadcrumbs_config", { input: payload });
|
||||
// Clear typed secrets from the UI after a successful save so a later
|
||||
// glance at the field doesn't look like a stored PSK came back.
|
||||
cfg!.networks = cfg!.networks.map((n) => ({ ...n, password: "" }));
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -93,12 +110,22 @@
|
|||
<NumberField label="Check connectivity every (s)" bind:value={cfg.settings.watch_interval} min={1} max={600} />
|
||||
</Group>
|
||||
|
||||
<Group title="Saved networks" wide>
|
||||
<Group
|
||||
title="Saved networks"
|
||||
hint="Password is write-only — leave it blank to keep an already-saved secret or let NetworkManager remember it after the first connect. Breadcrumbs never writes a PSK back into breadcrumbs.toml; new passwords go only to networks.toml (0600) and are cleared there after the first successful connect."
|
||||
wide
|
||||
>
|
||||
<div class="list">
|
||||
{#each cfg.networks as net, i (i)}
|
||||
<div class="net-row">
|
||||
<input type="text" bind:value={net.ssid} placeholder="Network name (SSID)" class="ssid" />
|
||||
<input type="password" bind:value={net.password} placeholder="Password" class="pass" />
|
||||
<input
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
bind:value={net.password}
|
||||
placeholder="New password (blank = keep / NM remembers)"
|
||||
class="pass"
|
||||
/>
|
||||
<div class="hidden-label">
|
||||
<Switch bind:value={net.hidden} ariaLabel="Hidden network" />
|
||||
<button type="button" class="label-text" onclick={() => (net.hidden = !net.hidden)}>Hidden network</button>
|
||||
|
|
|
|||
76
frontend/src/lib/views/Breadhelp.svelte
Normal file
76
frontend/src/lib/views/Breadhelp.svelte
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import ViewScaffold from "$lib/components/ViewScaffold.svelte";
|
||||
import Group from "$lib/components/Group.svelte";
|
||||
import Hint from "$lib/components/Hint.svelte";
|
||||
import SwitchField from "$lib/components/SwitchField.svelte";
|
||||
|
||||
interface AutostartEntry {
|
||||
command: string;
|
||||
label: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const BREADHELP_CMD = "breadhelp --autostart";
|
||||
const BREADHELP_LABEL = "BOS Help (first-run onboarding)";
|
||||
|
||||
let entries = $state<AutostartEntry[] | null>(null);
|
||||
let status = $state("");
|
||||
|
||||
let helpEntry = $derived(entries?.find((e) => e.command.trim().startsWith("breadhelp")) ?? null);
|
||||
let autostartOn = $derived(helpEntry?.enabled ?? false);
|
||||
|
||||
onMount(async () => {
|
||||
entries = await invoke<AutostartEntry[]>("get_autostart_entries");
|
||||
});
|
||||
|
||||
async function setAutostart(enabled: boolean) {
|
||||
if (!entries) return;
|
||||
const next = entries.map((e) => ({ ...e }));
|
||||
const i = next.findIndex((e) => e.command.trim().startsWith("breadhelp"));
|
||||
if (i >= 0) {
|
||||
next[i].enabled = enabled;
|
||||
} else if (enabled) {
|
||||
next.push({ command: BREADHELP_CMD, label: BREADHELP_LABEL, enabled: true });
|
||||
}
|
||||
try {
|
||||
await invoke("save_autostart_entries", { entries: next });
|
||||
entries = next;
|
||||
status = enabled ? "First-run help will launch at login." : "First-run help won't launch at login.";
|
||||
} catch (e) {
|
||||
status = `Error: ${e}`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ViewScaffold title="Help">
|
||||
<Group
|
||||
title="BOS Help"
|
||||
hint="breadhelp is the onboarding and help center — searchable guides, a keybind cheatsheet, a troubleshoot wizard, and a live tour. This panel just launches it; it doesn't copy the help app into Settings."
|
||||
>
|
||||
<button class="primary" onclick={() => invoke("open_breadhelp")}>Open breadhelp</button>
|
||||
</Group>
|
||||
|
||||
<Group title="First-run autostart" hint="The extra autostart entry in hypr/autostart.json (breadhelp --autostart). Core desktop launch is separate and always runs.">
|
||||
<SwitchField label="Show help on login" bind:value={() => autostartOn, (v) => setAutostart(v)} />
|
||||
{#if !helpEntry}
|
||||
<Hint text="No breadhelp entry in autostart.json yet — turning this on adds the default first-run command." />
|
||||
{/if}
|
||||
{#if status}
|
||||
<Hint text={status} />
|
||||
{/if}
|
||||
</Group>
|
||||
</ViewScaffold>
|
||||
|
||||
<style>
|
||||
.primary {
|
||||
align-self: flex-start;
|
||||
background-color: var(--accent);
|
||||
color: var(--on-accent);
|
||||
border: none;
|
||||
border-radius: var(--radius-primary, 8px);
|
||||
padding: var(--space-sm, 8px) var(--space-lg, 16px);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
94
frontend/src/lib/views/Breadlock.svelte
Normal file
94
frontend/src/lib/views/Breadlock.svelte
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import ViewScaffold from "$lib/components/ViewScaffold.svelte";
|
||||
import Group from "$lib/components/Group.svelte";
|
||||
import Hint from "$lib/components/Hint.svelte";
|
||||
import SelectField from "$lib/components/SelectField.svelte";
|
||||
import FileField from "$lib/components/FileField.svelte";
|
||||
import TextField from "$lib/components/TextField.svelte";
|
||||
import NumberField from "$lib/components/NumberField.svelte";
|
||||
import SwitchField from "$lib/components/SwitchField.svelte";
|
||||
import SaveButton from "$lib/components/SaveButton.svelte";
|
||||
|
||||
interface BreadlockConfig {
|
||||
background_mode: string;
|
||||
background_path: string;
|
||||
background_blur: boolean;
|
||||
clock_format: string;
|
||||
font_family: string;
|
||||
fail_timeout_ms: number;
|
||||
}
|
||||
|
||||
let cfg = $state<BreadlockConfig | null>(null);
|
||||
let examplePath = $state<string | null>(null);
|
||||
|
||||
onMount(async () => {
|
||||
cfg = await invoke<BreadlockConfig>("get_breadlock_config");
|
||||
examplePath = await invoke<string | null>("breadlock_example_path");
|
||||
});
|
||||
|
||||
async function save() {
|
||||
await invoke("save_breadlock_config", { cfg });
|
||||
}
|
||||
</script>
|
||||
|
||||
<ViewScaffold title="Lock & greet">
|
||||
<Group
|
||||
title="How locking works"
|
||||
hint="This panel does not configure PAM. Authentication is the breadlock PAM service (/etc/pam.d/breadlock) plus greetd for login — both are packaged, not user-editable from Settings."
|
||||
>
|
||||
<Hint
|
||||
text="Super+L runs loginctl lock-session. hypridle's lock_cmd / idle listener then starts breadlock, which owns the already-running Hyprland session via ext-session-lock-v1."
|
||||
/>
|
||||
<Hint
|
||||
text="breadgreet is the graphical greetd greeter (replacing tuigreet). Its live config is typically /etc/greetd/breadgreet.toml, owned by the greeter user — not written from here."
|
||||
/>
|
||||
<button class="primary" onclick={() => invoke("lock_session")}>Lock now</button>
|
||||
</Group>
|
||||
|
||||
{#if cfg}
|
||||
<Group title="Lock screen" hint="~/.config/breadlock/breadlock.toml — every field is optional; breadlock runs with these defaults if the file is missing.">
|
||||
<SelectField label="Background" bind:value={cfg.background_mode} options={["color", "image"]} />
|
||||
{#if cfg.background_mode === "image"}
|
||||
<FileField label="Image" bind:value={cfg.background_path} placeholder="PNG, cover-fit" extensions={["png"]} />
|
||||
{/if}
|
||||
<SwitchField label="Blur background" bind:value={cfg.background_blur} />
|
||||
<Hint text="Blur is accepted in the file but not implemented yet (needs a wlr-screencopy capture). breadlock logs a warning and shows the background unblurred." />
|
||||
<TextField label="Clock format" bind:value={cfg.clock_format} placeholder="%H:%M" />
|
||||
<TextField label="Font" bind:value={cfg.font_family} placeholder="Varela Round" />
|
||||
<NumberField label="Wrong-password timeout (ms)" bind:value={cfg.fail_timeout_ms} min={0} max={10000} />
|
||||
<SaveButton onSave={save} />
|
||||
</Group>
|
||||
{/if}
|
||||
|
||||
<Group title="Files">
|
||||
<button class="secondary" onclick={() => invoke("open_breadlock_config")}>Open breadlock.toml in editor</button>
|
||||
{#if examplePath}
|
||||
<button class="secondary" onclick={() => invoke("open_breadlock_example")}>Open example ({examplePath})</button>
|
||||
{:else}
|
||||
<Hint text="No packaged breadlock.example.toml found. The schema is [background] mode/path/blur, [clock] format, [font] family, [input] fail_timeout_ms." />
|
||||
{/if}
|
||||
</Group>
|
||||
</ViewScaffold>
|
||||
|
||||
<style>
|
||||
button {
|
||||
border: none;
|
||||
border-radius: var(--radius-primary, 8px);
|
||||
padding: var(--space-sm, 8px) var(--space-lg, 16px);
|
||||
cursor: pointer;
|
||||
align-self: flex-start;
|
||||
margin-top: var(--space-xs, 4px);
|
||||
}
|
||||
|
||||
.primary {
|
||||
background-color: var(--accent);
|
||||
color: var(--on-accent);
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background-color: var(--surface);
|
||||
color: var(--on-surface);
|
||||
}
|
||||
</style>
|
||||
34
frontend/src/lib/views/Breadmon.svelte
Normal file
34
frontend/src/lib/views/Breadmon.svelte
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<script lang="ts">
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import ViewScaffold from "$lib/components/ViewScaffold.svelte";
|
||||
import Group from "$lib/components/Group.svelte";
|
||||
import Hint from "$lib/components/Hint.svelte";
|
||||
</script>
|
||||
|
||||
<ViewScaffold title="Monitors">
|
||||
<Group
|
||||
title="Two different jobs"
|
||||
hint="This is not a second Display editor. Display (monitors.json) is the login-time layout Hyprland itself reads. breadmon is a TUI for live arrange / mirror / named profiles."
|
||||
>
|
||||
<Hint
|
||||
text="Use Display when you want the persistent Hyprland rule set (output / mode / position / scale) that applies on next login or hyprctl reload."
|
||||
/>
|
||||
<Hint
|
||||
text="Use breadmon when you want to drag monitors around live, pick a common mirror mode, or save/load a profile under ~/.config/breadmon/profiles/. Settings never writes those profiles."
|
||||
/>
|
||||
<button class="primary" onclick={() => invoke("open_breadmon")}>Open breadmon</button>
|
||||
</Group>
|
||||
</ViewScaffold>
|
||||
|
||||
<style>
|
||||
.primary {
|
||||
align-self: flex-start;
|
||||
margin-top: var(--space-sm, 8px);
|
||||
background-color: var(--accent);
|
||||
color: var(--on-accent);
|
||||
border: none;
|
||||
border-radius: var(--radius-primary, 8px);
|
||||
padding: var(--space-sm, 8px) var(--space-lg, 16px);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
78
frontend/src/lib/views/Breadshot.svelte
Normal file
78
frontend/src/lib/views/Breadshot.svelte
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import ViewScaffold from "$lib/components/ViewScaffold.svelte";
|
||||
import Group from "$lib/components/Group.svelte";
|
||||
import Hint from "$lib/components/Hint.svelte";
|
||||
import InfoRow from "$lib/components/InfoRow.svelte";
|
||||
import FileField from "$lib/components/FileField.svelte";
|
||||
import TextField from "$lib/components/TextField.svelte";
|
||||
import NumberField from "$lib/components/NumberField.svelte";
|
||||
import SwitchField from "$lib/components/SwitchField.svelte";
|
||||
import SaveButton from "$lib/components/SaveButton.svelte";
|
||||
|
||||
interface ShotBind {
|
||||
shortcut: string;
|
||||
command: string;
|
||||
}
|
||||
interface BreadshotConfig {
|
||||
save_dir: string;
|
||||
silent: boolean;
|
||||
freeze: boolean;
|
||||
notif_timeout: number;
|
||||
date_format: string;
|
||||
}
|
||||
|
||||
let binds = $state<ShotBind[] | null>(null);
|
||||
let cfg = $state<BreadshotConfig | null>(null);
|
||||
|
||||
onMount(async () => {
|
||||
binds = await invoke<ShotBind[]>("get_breadshot_binds");
|
||||
cfg = await invoke<BreadshotConfig>("get_breadshot_config");
|
||||
});
|
||||
|
||||
async function save() {
|
||||
await invoke("save_breadshot_config", { cfg });
|
||||
}
|
||||
</script>
|
||||
|
||||
<ViewScaffold title="Screenshots">
|
||||
<Group
|
||||
title="Keybinds"
|
||||
hint="Read-only from binds.json. Super+Shift+S/C/P is the BOS default (region→file / region→clipboard / screen→file). Change them on the Keybinds panel."
|
||||
>
|
||||
{#if binds && binds.length > 0}
|
||||
{#each binds as b (`${b.shortcut}:${b.command}`)}
|
||||
<InfoRow label={b.shortcut} value={b.command} />
|
||||
{/each}
|
||||
{:else}
|
||||
<Hint text="No breadshot binds found." />
|
||||
{/if}
|
||||
<button class="primary" onclick={() => invoke("breadshot_region_clipboard")}>Capture region to clipboard</button>
|
||||
</Group>
|
||||
|
||||
{#if cfg}
|
||||
<Group title="breadshot" hint="~/.config/breadshot/config.toml — grim + slurp + wl-copy, Hyprland-aware. All keys optional.">
|
||||
<FileField label="Save directory" bind:value={cfg.save_dir} placeholder="~/Pictures/Screenshots" mode="folder" />
|
||||
<SwitchField label="Silent (no notifications)" bind:value={cfg.silent} />
|
||||
<SwitchField label="Freeze screen during select" bind:value={cfg.freeze} />
|
||||
<Hint text="Freeze needs hyprpicker. Filenames are <date_format>_breadshot.png." />
|
||||
<NumberField label="Notification timeout (ms)" bind:value={cfg.notif_timeout} min={0} max={60000} />
|
||||
<TextField label="Filename date format" bind:value={cfg.date_format} placeholder="%Y-%m-%d-%H%M%S" />
|
||||
<SaveButton onSave={save} />
|
||||
</Group>
|
||||
{/if}
|
||||
</ViewScaffold>
|
||||
|
||||
<style>
|
||||
.primary {
|
||||
align-self: flex-start;
|
||||
margin-top: var(--space-sm, 8px);
|
||||
background-color: var(--accent);
|
||||
color: var(--on-accent);
|
||||
border: none;
|
||||
border-radius: var(--radius-primary, 8px);
|
||||
padding: var(--space-sm, 8px) var(--space-lg, 16px);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { runStreamingCommand } from "$lib/streaming";
|
||||
import { runStreamed } from "$lib/streaming";
|
||||
import ViewScaffold from "$lib/components/ViewScaffold.svelte";
|
||||
import Group from "$lib/components/Group.svelte";
|
||||
import Hint from "$lib/components/Hint.svelte";
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
async function checkForUpdates() {
|
||||
log = [];
|
||||
busy = true;
|
||||
await runStreamingCommand("fwupdmgr", ["refresh"], appendLine);
|
||||
await runStreamed("fwupd_refresh", {}, appendLine);
|
||||
busy = false;
|
||||
await refresh();
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
async function updateAll() {
|
||||
log = [];
|
||||
busy = true;
|
||||
await runStreamingCommand("fwupdmgr", ["update", "-y"], appendLine);
|
||||
await runStreamed("fwupd_update", {}, appendLine);
|
||||
busy = false;
|
||||
await refresh();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { runStreamingCommand } from "$lib/streaming";
|
||||
import { runStreamed } from "$lib/streaming";
|
||||
import ViewScaffold from "$lib/components/ViewScaffold.svelte";
|
||||
import Group from "$lib/components/Group.svelte";
|
||||
import Hint from "$lib/components/Hint.svelte";
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
async function updatePackage(name: string) {
|
||||
log = [];
|
||||
busy = true;
|
||||
await runStreamingCommand("bakery", ["update", name], appendLine);
|
||||
await runStreamed("bakery_update", { name }, appendLine);
|
||||
busy = false;
|
||||
await refresh();
|
||||
}
|
||||
|
|
@ -39,14 +39,14 @@
|
|||
async function listInstalled() {
|
||||
log = [];
|
||||
busy = true;
|
||||
await runStreamingCommand("bakery", ["list"], appendLine);
|
||||
await runStreamed("bakery_list", {}, appendLine);
|
||||
busy = false;
|
||||
}
|
||||
|
||||
async function updateAll() {
|
||||
log = [];
|
||||
busy = true;
|
||||
await runStreamingCommand("bakery", ["update", "--all"], appendLine);
|
||||
await runStreamed("bakery_update_all", {}, appendLine);
|
||||
busy = false;
|
||||
await refresh();
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
async function updateSystem() {
|
||||
log = [];
|
||||
busy = true;
|
||||
await runStreamingCommand("pkexec", ["pacman", "-Syu", "--noconfirm"], appendLine);
|
||||
await runStreamed("pacman_system_update", {}, appendLine);
|
||||
busy = false;
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// Maps a sidebar page id to its view component. Pages not yet migrated
|
||||
// fall back to Placeholder (see +page.svelte) — this map only lists pages
|
||||
// that actually have a real Tauri-backed view.
|
||||
// Maps a sidebar page id to its view component. Every sidebar id has a
|
||||
// real view — +page.svelte's Placeholder is only a safety net for typos.
|
||||
|
||||
import type { Component } from "svelte";
|
||||
import About from "./About.svelte";
|
||||
|
|
@ -27,6 +26,10 @@ import Packages from "./Packages.svelte";
|
|||
import Aur from "./Aur.svelte";
|
||||
import Firmware from "./Firmware.svelte";
|
||||
import Snapshots from "./Snapshots.svelte";
|
||||
import Breadlock from "./Breadlock.svelte";
|
||||
import Breadshot from "./Breadshot.svelte";
|
||||
import Breadmon from "./Breadmon.svelte";
|
||||
import Breadhelp from "./Breadhelp.svelte";
|
||||
|
||||
export const VIEWS: Record<string, Component> = {
|
||||
about: About,
|
||||
|
|
@ -53,4 +56,8 @@ export const VIEWS: Record<string, Component> = {
|
|||
aur: Aur,
|
||||
firmware: Firmware,
|
||||
snapshots: Snapshots,
|
||||
breadlock: Breadlock,
|
||||
breadshot: Breadshot,
|
||||
breadmon: Breadmon,
|
||||
breadhelp: Breadhelp,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue