ci: add multi-OS release workflow with auto-updater

- GitHub Action builds for Linux, Windows, macOS on v* tag push
- macOS universal binary (Intel + Apple Silicon)
- Draft release with all installers + updater signatures
- tauri-plugin-updater for built-in auto-update support
This commit is contained in:
Matt
2026-08-25 18:25:05 +00:00
parent 717911b18b
commit 18783eaf28
23 changed files with 516 additions and 90 deletions
+65
View File
@@ -0,0 +1,65 @@
name: Build & Release
on:
push:
tags:
- "v*"
permissions:
contents: write
jobs:
release:
strategy:
fail-fast: false
matrix:
include:
- platform: ubuntu-22.04
args: ""
- platform: windows-latest
args: ""
- platform: macos-latest
args: "--target universal-apple-darwin"
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install frontend dependencies
run: npm ci
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Install Linux dependencies
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: Build and release
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tagName: ${{ github.ref_name }}
releaseName: "Clustri ${{ github.ref_name }}"
releaseBody: "See the assets below to download and install."
releaseDraft: true
prerelease: false
updaterJsonPreferNsis: true
args: ${{ matrix.args }}
+4 -4
View File
@@ -4,7 +4,7 @@ const navItems = [
{ label: 'Dashboard', icon: 'grid', active: true }, { label: 'Dashboard', icon: 'grid', active: true },
{ label: 'Nodes', icon: 'server', active: false }, { label: 'Nodes', icon: 'server', active: false },
{ label: 'VMs', icon: 'box', active: false }, { label: 'VMs', icon: 'box', active: false },
{ label: 'Containers', icon: 'box', active: false }, { label: 'CTs', icon: 'box', active: false },
{ label: 'Storage', icon: 'hdd', active: false }, { label: 'Storage', icon: 'hdd', active: false },
{ label: 'Tasks', icon: 'todo', active: false }, { label: 'Tasks', icon: 'todo', active: false },
{ label: 'Backups', icon: 'shield', active: false }, { label: 'Backups', icon: 'shield', active: false },
@@ -110,7 +110,7 @@ const quickActions = [
<div class="flex items-center gap-2 border-b border-warning/25 bg-warning/10 px-4 py-1.5"> <div class="flex items-center gap-2 border-b border-warning/25 bg-warning/10 px-4 py-1.5">
<span class="h-1.5 w-1.5 shrink-0 rounded-full bg-warning" aria-hidden="true"></span> <span class="h-1.5 w-1.5 shrink-0 rounded-full bg-warning" aria-hidden="true"></span>
<p class="shrink-0 font-mono text-[10px] text-warning">Operating on a fallback node</p> <p class="shrink-0 font-mono text-[10px] text-warning">Using a fallback node</p>
<p class="ml-auto min-w-0 truncate font-mono text-[10px] tabular-nums text-warning/80">https://192.168.1.12:8006</p> <p class="ml-auto min-w-0 truncate font-mono text-[10px] tabular-nums text-warning/80">https://192.168.1.12:8006</p>
</div> </div>
@@ -146,7 +146,7 @@ const quickActions = [
</div> </div>
<p class="ml-7 font-mono text-[10px] text-muted-foreground">Offline</p> <p class="ml-7 font-mono text-[10px] text-muted-foreground">Offline</p>
</div> </div>
<div class="px-3 pb-1 pt-3"><DotMatrixText text="CLUSTER NODES" size="xs" dotClass="text-muted-foreground" /></div> <div class="px-3 pb-1 pt-3"><DotMatrixText text="NODES IN CLUSTER" size="xs" dotClass="text-muted-foreground" /></div>
<div class="space-y-0.5"> <div class="space-y-0.5">
{clusterNodes.map((n) => ( {clusterNodes.map((n) => (
<div class="flex w-full items-center gap-2.5 rounded-md px-3 py-1.5"> <div class="flex w-full items-center gap-2.5 rounded-md px-3 py-1.5">
@@ -219,7 +219,7 @@ const quickActions = [
<section class="rounded-md border border-dotted border-white/10 bg-card sm:col-span-3"> <section class="rounded-md border border-dotted border-white/10 bg-card sm:col-span-3">
<header class="flex items-center gap-2 border-b border-dotted border-white/10 px-3 py-2"> <header class="flex items-center gap-2 border-b border-dotted border-white/10 px-3 py-2">
<svg class="h-3.5 w-3.5 shrink-0 text-muted-foreground" aria-hidden="true"><use href="#i-activity" /></svg> <svg class="h-3.5 w-3.5 shrink-0 text-muted-foreground" aria-hidden="true"><use href="#i-activity" /></svg>
<h3 class="font-mono text-[10px] font-medium uppercase tracking-[0.14em] text-muted-foreground">Task activity</h3> <h3 class="font-mono text-[10px] font-medium uppercase tracking-[0.14em] text-muted-foreground">Recent tasks</h3>
<span class="ml-auto h-1.5 w-1.5 animate-pulse rounded-full bg-success" aria-hidden="true"></span> <span class="ml-auto h-1.5 w-1.5 animate-pulse rounded-full bg-success" aria-hidden="true"></span>
</header> </header>
<div class="divide-y divide-border" data-live-tasks> <div class="divide-y divide-border" data-live-tasks>
+3 -3
View File
@@ -5,8 +5,8 @@ import DotMatrixText from './DotMatrixText.astro'
<div class="mx-auto max-w-7xl px-6 lg:px-8"> <div class="mx-auto max-w-7xl px-6 lg:px-8">
<div class="max-w-2xl" data-reveal> <div class="max-w-2xl" data-reveal>
<DotMatrixText text="ARCHITECTURE" size="xs" dotClass="text-primary" /> <DotMatrixText text="ARCHITECTURE" size="xs" dotClass="text-primary" />
<h2 id="arch-title" class="mt-4 text-3xl font-semibold tracking-[-0.03em] text-foreground sm:text-4xl">One proxy between you and the API.</h2> <h2 id="arch-title" class="mt-4 text-3xl font-semibold tracking-[-0.03em] text-foreground sm:text-4xl">How Clustri reaches Proxmox.</h2>
<p class="mt-4 text-[15px] leading-relaxed text-muted-foreground">The frontend calls Rust commands over Tauri IPC. The backend proxies them to the Proxmox API over HTTPS.</p> <p class="mt-4 text-[15px] leading-relaxed text-muted-foreground">The React frontend calls Rust commands through Tauri IPC. The Rust backend sends each request to Proxmox over HTTPS.</p>
</div> </div>
<div class="mt-10 grid gap-3 lg:grid-cols-[1fr_auto_1fr_auto_1fr] lg:items-stretch" data-stagger> <div class="mt-10 grid gap-3 lg:grid-cols-[1fr_auto_1fr_auto_1fr] lg:items-stretch" data-stagger>
@@ -43,7 +43,7 @@ import DotMatrixText from './DotMatrixText.astro'
</div> </div>
<div class="py-2" data-reveal data-reveal-delay="100"> <div class="py-2" data-reveal data-reveal-delay="100">
<DotMatrixText text="FAILOVER MODEL" size="xs" dotClass="text-muted-foreground" /> <DotMatrixText text="FAILOVER MODEL" size="xs" dotClass="text-muted-foreground" />
<p class="mt-3 text-sm leading-relaxed text-muted-foreground">Ordered, deduped endpoints: primary fallbacks discovered nodes. Connect / timeout / DNS / transport failure → retry next endpoint.</p> <p class="mt-3 text-sm leading-relaxed text-muted-foreground">Clustri keeps an ordered list of unique endpoints: primary, fallbacks, then discovered nodes. Connection, timeout, DNS, or transport errors trigger the next attempt.</p>
<div class="mt-4 flex gap-4 font-mono text-[10px]"> <div class="mt-4 flex gap-4 font-mono text-[10px]">
<span class="inline-flex items-center gap-1.5"><span class="h-1.5 w-1.5 rounded-full bg-success"></span> connected</span> <span class="inline-flex items-center gap-1.5"><span class="h-1.5 w-1.5 rounded-full bg-success"></span> connected</span>
<span class="inline-flex items-center gap-1.5"><span class="h-1.5 w-1.5 rounded-full bg-warning"></span> failover</span> <span class="inline-flex items-center gap-1.5"><span class="h-1.5 w-1.5 rounded-full bg-warning"></span> failover</span>
+2 -2
View File
@@ -5,12 +5,12 @@ import DotMatrixText from './DotMatrixText.astro'
<div id="cmdk-panel" class="cmdk-panel w-full max-w-[520px] overflow-hidden rounded-xl border border-dotted border-white/10 bg-card dot-grid"> <div id="cmdk-panel" class="cmdk-panel w-full max-w-[520px] overflow-hidden rounded-xl border border-dotted border-white/10 bg-card dot-grid">
<div class="flex items-center gap-3 border-b border-dotted border-white/10 px-4"> <div class="flex items-center gap-3 border-b border-dotted border-white/10 px-4">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="h-4 w-4 shrink-0 text-muted-foreground" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="h-4 w-4 shrink-0 text-muted-foreground" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
<input id="cmdk-input" type="text" placeholder="Type a command" autocomplete="off" spellcheck="false" class="h-12 w-full bg-transparent font-mono text-sm text-foreground placeholder:text-muted-foreground/60 focus:outline-none" /> <input id="cmdk-input" type="text" placeholder="Search commands" autocomplete="off" spellcheck="false" class="h-12 w-full bg-transparent font-mono text-sm text-foreground placeholder:text-muted-foreground/60 focus:outline-none" />
<kbd class="hidden rounded border border-dotted border-white/10 bg-white/[0.04] px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground sm:inline">ESC</kbd> <kbd class="hidden rounded border border-dotted border-white/10 bg-white/[0.04] px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground sm:inline">ESC</kbd>
</div> </div>
<div id="cmdk-list" class="max-h-[320px] overflow-auto p-2"></div> <div id="cmdk-list" class="max-h-[320px] overflow-auto p-2"></div>
<div class="border-t border-dotted border-white/10 px-3 py-2 flex items-center justify-between"> <div class="border-t border-dotted border-white/10 px-3 py-2 flex items-center justify-between">
<DotMatrixText text="UP/DN NAVIGATE / ENTER SELECT / ESC CLOSE" size="xs" dotClass="text-muted-foreground" /> <DotMatrixText text="ARROWS MOVE / ENTER SELECT / ESC CLOSE" size="xs" dotClass="text-muted-foreground" />
</div> </div>
</div> </div>
</div> </div>
+4 -4
View File
@@ -4,9 +4,9 @@ import DotMatrixText from './DotMatrixText.astro'
<section id="download" class="scroll-mt-24 border-t border-dotted border-white/10 py-20 sm:py-28"> <section id="download" class="scroll-mt-24 border-t border-dotted border-white/10 py-20 sm:py-28">
<div class="mx-auto max-w-3xl px-6 text-center"> <div class="mx-auto max-w-3xl px-6 text-center">
<div data-reveal><DotMatrixText text="RELEASES" size="xs" dotClass="text-primary" /></div> <div data-reveal><DotMatrixText text="RELEASES" size="xs" dotClass="text-primary" /></div>
<h2 class="mt-4 text-3xl font-semibold tracking-[-0.03em] text-foreground sm:text-4xl" data-reveal data-reveal-delay="80">Coming soon.</h2> <h2 class="mt-4 text-3xl font-semibold tracking-[-0.03em] text-foreground sm:text-4xl" data-reveal data-reveal-delay="80">Packages are not published yet.</h2>
<p class="mx-auto mt-4 max-w-[46ch] text-[15px] leading-relaxed text-muted-foreground" data-reveal data-reveal-delay="140"> <p class="mx-auto mt-4 max-w-[46ch] text-[15px] leading-relaxed text-muted-foreground" data-reveal data-reveal-delay="140">
Clustri is in active development. Packages for Linux, macOS, and Windows are on the way. Clustri is under active development. Linux, macOS, and Windows packages are next.
</p> </p>
<div class="relative mt-10 overflow-hidden rounded-xl border border-dotted border-white/10 bg-card p-6 sm:p-8 shadow-card dot-grid" data-reveal data-reveal-delay="200"> <div class="relative mt-10 overflow-hidden rounded-xl border border-dotted border-white/10 bg-card p-6 sm:p-8 shadow-card dot-grid" data-reveal data-reveal-delay="200">
@@ -15,8 +15,8 @@ import DotMatrixText from './DotMatrixText.astro'
<div class="relative"> <div class="relative">
<div class="flex flex-wrap items-center justify-center gap-3"> <div class="flex flex-wrap items-center justify-center gap-3">
<a href="#" class="rounded-[10px] bg-primary px-6 py-3 text-sm font-semibold text-primary-foreground transition hover:bg-primary/90 active:scale-[0.98]">Get Clustri</a> <a href="https://github.com/Factor-scd/clustri/releases/latest" class="rounded-[10px] bg-primary px-6 py-3 text-sm font-semibold text-primary-foreground transition hover:bg-primary/90 active:scale-[0.98]">Download from GitHub</a>
<a href="#" class="rounded-[10px] border border-dotted border-white/10 bg-white/[0.03] px-6 py-3 text-sm font-semibold text-muted-foreground transition hover:border-white/15 hover:text-foreground active:scale-[0.98]">Build from source</a> <a href="https://github.com/Factor-scd/clustri" class="rounded-[10px] border border-dotted border-white/10 bg-white/[0.03] px-6 py-3 text-sm font-semibold text-muted-foreground transition hover:border-white/15 hover:text-foreground active:scale-[0.98]">Build from source</a>
</div> </div>
<div class="mt-6 flex flex-wrap items-center justify-center gap-2"> <div class="mt-6 flex flex-wrap items-center justify-center gap-2">
<span class="rounded-md border border-dotted border-white/10 bg-background px-2.5 py-1 font-mono text-[11px] text-muted-foreground">Linux</span> <span class="rounded-md border border-dotted border-white/10 bg-background px-2.5 py-1 font-mono text-[11px] text-muted-foreground">Linux</span>
+14 -14
View File
@@ -9,28 +9,28 @@ import DotMatrixText from './DotMatrixText.astro'
<div class="lg:col-span-5"> <div class="lg:col-span-5">
<div data-reveal><DotMatrixText text="FAILOVER / TRY IT" size="xs" dotClass="text-primary" /></div> <div data-reveal><DotMatrixText text="FAILOVER / TRY IT" size="xs" dotClass="text-primary" /></div>
<h2 id="fp-title" class="mt-4 text-3xl font-semibold tracking-[-0.03em] text-foreground sm:text-4xl" data-reveal data-reveal-delay="80"> <h2 id="fp-title" class="mt-4 text-3xl font-semibold tracking-[-0.03em] text-foreground sm:text-4xl" data-reveal data-reveal-delay="80">
One node dies.<br /><span class="text-muted-foreground">You don't notice.</span> When a node drops,<br /><span class="text-muted-foreground">requests keep moving.</span>
</h2> </h2>
<p class="mt-4 max-w-[42ch] text-[15px] leading-relaxed text-muted-foreground" data-reveal data-reveal-delay="140"> <p class="mt-4 max-w-[42ch] text-[15px] leading-relaxed text-muted-foreground" data-reveal data-reveal-delay="140">
Requests ride an ordered endpoint list: primary, fallbacks, then discovered cluster nodes. Kill a node on the right watch the packets reroute and the status flip. Clustri tries the primary endpoint, then configured fallbacks and discovered cluster nodes. Stop a node on the right to watch the request move.
</p> </p>
<div class="mt-8 flex flex-wrap gap-2" data-reveal data-reveal-delay="180"> <div class="mt-8 flex flex-wrap gap-2" data-reveal data-reveal-delay="180">
<button id="fp-kill-pve1" type="button" class="rounded-md border border-dotted border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-[11px] font-medium text-foreground transition hover:border-white/15 hover:bg-white/[0.07]">Kill pve1</button> <button id="fp-kill-pve1" type="button" class="rounded-md border border-dotted border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-[11px] font-medium text-foreground transition hover:border-white/15 hover:bg-white/[0.07]">Stop pve1</button>
<button id="fp-kill-pve2" type="button" class="rounded-md border border-dotted border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-[11px] font-medium text-foreground transition hover:border-white/15 hover:bg-white/[0.07]">Kill pve2</button> <button id="fp-kill-pve2" type="button" class="rounded-md border border-dotted border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-[11px] font-medium text-foreground transition hover:border-white/15 hover:bg-white/[0.07]">Stop pve2</button>
<button id="fp-kill-pve3" type="button" class="rounded-md border border-dotted border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-[11px] font-medium text-foreground transition hover:border-white/15 hover:bg-white/[0.07]">Kill pve3</button> <button id="fp-kill-pve3" type="button" class="rounded-md border border-dotted border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-[11px] font-medium text-foreground transition hover:border-white/15 hover:bg-white/[0.07]">Stop pve3</button>
<button id="fp-heal" type="button" class="rounded-md bg-primary px-3 py-2 font-mono text-[11px] font-semibold text-primary-foreground transition hover:bg-primary/90">Heal all</button> <button id="fp-heal" type="button" class="rounded-md bg-primary px-3 py-2 font-mono text-[11px] font-semibold text-primary-foreground transition hover:bg-primary/90">Restore nodes</button>
</div> </div>
<div class="mt-6 flex flex-wrap gap-3 font-mono text-[10px]" data-reveal data-reveal-delay="220"> <div class="mt-6 flex flex-wrap gap-3 font-mono text-[10px]" data-reveal data-reveal-delay="220">
<span class="inline-flex items-center gap-1.5"><span class="h-1.5 w-1.5 rounded-full bg-success"></span> connected</span> <span class="inline-flex items-center gap-1.5"><span class="h-1.5 w-1.5 rounded-full bg-success"></span> connected</span>
<span class="inline-flex items-center gap-1.5"><span class="h-1.5 w-1.5 rounded-full bg-warning"></span> failover</span> <span class="inline-flex items-center gap-1.5"><span class="h-1.5 w-1.5 rounded-full bg-warning"></span> failover</span>
<span class="inline-flex items-center gap-1.5"><span class="h-1.5 w-1.5 rounded-full bg-destructive"></span> failed</span> <span class="inline-flex items-center gap-1.5"><span class="h-1.5 w-1.5 rounded-full bg-destructive"></span> failed</span>
<span class="ml-1 text-muted-foreground">— tap a node or use the buttons</span> <span class="ml-1 text-muted-foreground">Click a node or use a button</span>
</div> </div>
<div class="mt-6 rounded-md border border-dotted border-white/10 bg-background px-3 py-2.5 font-mono text-[11px] leading-relaxed text-muted-foreground" data-reveal data-reveal-delay="260"> <div class="mt-6 rounded-md border border-dotted border-white/10 bg-background px-3 py-2.5 font-mono text-[11px] leading-relaxed text-muted-foreground" data-reveal data-reveal-delay="260">
<span class="text-foreground">How it works:</span> deduped order <span class="text-foreground">primary → fallbacks → discovered</span>. On connect / timeout / DNS / transport failure, retry the next endpoint. Status reflects which endpoint actually answered. <span class="text-foreground">Request order:</span> <span class="text-foreground">primary → fallbacks → discovered</span>. A connection, timeout, DNS, or transport failure moves the request to the next endpoint.
</div> </div>
</div> </div>
@@ -42,7 +42,7 @@ import DotMatrixText from './DotMatrixText.astro'
<!-- header --> <!-- header -->
<div class="flex items-center justify-between gap-3 border-b border-dotted border-white/10 px-4 py-3"> <div class="flex items-center justify-between gap-3 border-b border-dotted border-white/10 px-4 py-3">
<p class="font-mono text-[10px] tracking-[0.06em] text-muted-foreground">request → endpoints[0] → retry on failure</p> <p class="font-mono text-[10px] tracking-[0.06em] text-muted-foreground">request → primary endpoint → retry if needed</p>
<span id="fp-status-chip" class="rounded border border-success/25 bg-success/10 px-1.5 py-0.5 font-mono text-[10px] uppercase text-success">Connected</span> <span id="fp-status-chip" class="rounded border border-success/25 bg-success/10 px-1.5 py-0.5 font-mono text-[10px] uppercase text-success">Connected</span>
</div> </div>
@@ -94,7 +94,7 @@ import DotMatrixText from './DotMatrixText.astro'
<!-- status bar --> <!-- status bar -->
<div class="border-t border-dotted border-white/10 bg-background px-4 py-3"> <div class="border-t border-dotted border-white/10 bg-background px-4 py-3">
<div class="flex items-center justify-between gap-3"> <div class="flex items-center justify-between gap-3">
<span id="fp-status-text" class="font-mono text-[11px] tabular-nums text-muted-foreground">status: connected pve1 answered</span> <span id="fp-status-text" class="font-mono text-[11px] tabular-nums text-muted-foreground">status: connected, pve1 answered</span>
<span id="fp-latency" class="font-mono text-[10px] tabular-nums text-muted-foreground">~18 ms</span> <span id="fp-latency" class="font-mono text-[10px] tabular-nums text-muted-foreground">~18 ms</span>
</div> </div>
<div class="mt-2 h-1 overflow-hidden rounded-full bg-muted"> <div class="mt-2 h-1 overflow-hidden rounded-full bg-muted">
@@ -103,7 +103,7 @@ import DotMatrixText from './DotMatrixText.astro'
</div> </div>
</div> </div>
<p class="mt-3 text-center font-mono text-[10px] text-muted-foreground/60">Click a node row to kill / heal it. Packets reroute automatically.</p> <p class="mt-3 text-center font-mono text-[10px] text-muted-foreground/60">Click a node row to stop or restore it. Requests move to the next endpoint.</p>
</div> </div>
</div> </div>
</div> </div>
@@ -172,7 +172,7 @@ import DotMatrixText from './DotMatrixText.astro'
if (failed) { if (failed) {
chip.textContent = 'Failed' chip.textContent = 'Failed'
chip.className = 'rounded border border-destructive/30 bg-destructive/10 px-1.5 py-0.5 font-mono text-[10px] uppercase text-destructive' chip.className = 'rounded border border-destructive/30 bg-destructive/10 px-1.5 py-0.5 font-mono text-[10px] uppercase text-destructive'
statusText.textContent = 'status: failed no endpoint reachable' statusText.textContent = 'status: failed, no endpoint reachable'
statusText.className = 'font-mono text-[11px] tabular-nums text-destructive' statusText.className = 'font-mono text-[11px] tabular-nums text-destructive'
topRule.className = 'h-px w-full bg-destructive/40' topRule.className = 'h-px w-full bg-destructive/40'
bar.style.width = '100%' bar.style.width = '100%'
@@ -181,7 +181,7 @@ import DotMatrixText from './DotMatrixText.astro'
} else if (failover) { } else if (failover) {
chip.textContent = 'Failover' chip.textContent = 'Failover'
chip.className = 'rounded border border-warning/30 bg-warning/10 px-1.5 py-0.5 font-mono text-[10px] uppercase text-warning' chip.className = 'rounded border border-warning/30 bg-warning/10 px-1.5 py-0.5 font-mono text-[10px] uppercase text-warning'
statusText.textContent = `status: failover ${names[ai]} answered (${addrs[ai]})` statusText.textContent = `status: failover, ${names[ai]} answered (${addrs[ai]})`
statusText.className = 'font-mono text-[11px] tabular-nums text-warning' statusText.className = 'font-mono text-[11px] tabular-nums text-warning'
topRule.className = 'h-px w-full bg-warning/40' topRule.className = 'h-px w-full bg-warning/40'
bar.style.width = ai === 1 ? '56%' : '88%' bar.style.width = ai === 1 ? '56%' : '88%'
@@ -190,7 +190,7 @@ import DotMatrixText from './DotMatrixText.astro'
} else { } else {
chip.textContent = 'Connected' chip.textContent = 'Connected'
chip.className = 'rounded border border-success/25 bg-success/10 px-1.5 py-0.5 font-mono text-[10px] uppercase text-success' chip.className = 'rounded border border-success/25 bg-success/10 px-1.5 py-0.5 font-mono text-[10px] uppercase text-success'
statusText.textContent = `status: connected ${names[0]} answered` statusText.textContent = `status: connected, ${names[0]} answered`
statusText.className = 'font-mono text-[11px] tabular-nums text-success' statusText.className = 'font-mono text-[11px] tabular-nums text-success'
topRule.className = 'h-px w-full bg-primary/30' topRule.className = 'h-px w-full bg-primary/30'
bar.style.width = '18%' bar.style.width = '18%'
+16 -16
View File
@@ -8,15 +8,15 @@ import DotMatrixText from './DotMatrixText.astro'
<div class="lg:col-span-6" data-reveal> <div class="lg:col-span-6" data-reveal>
<DotMatrixText text="SECURITY" size="xs" dotClass="text-primary" /> <DotMatrixText text="SECURITY" size="xs" dotClass="text-primary" />
<h2 id="sec-title" class="mt-4 text-3xl font-semibold tracking-[-0.03em] text-foreground sm:text-4xl"> <h2 id="sec-title" class="mt-4 text-3xl font-semibold tracking-[-0.03em] text-foreground sm:text-4xl">
Your keys stay<br /><span class="text-muted-foreground">in your pocket.</span> Keep credentials<br /><span class="text-muted-foreground">on your machine.</span>
</h2> </h2>
<p class="mt-4 max-w-[44ch] text-[15px] leading-relaxed text-muted-foreground"> <p class="mt-4 max-w-[44ch] text-[15px] leading-relaxed text-muted-foreground">
API tokens live in the OS keyring — never on disk. Self-signed certs are pinned by SHA-256 on first connect and verified every time after. Clustri stores API tokens in the OS keyring, not in a file. It pins self-signed certificates by SHA-256 on first connect and checks them on later connections.
</p> </p>
<div class="mt-6 space-y-3 font-mono text-[11px] leading-relaxed"> <div class="mt-6 space-y-3 font-mono text-[11px] leading-relaxed">
<div class="flex gap-2"><span class="text-primary"></span><span class="text-muted-foreground">Token mode: <span class="text-foreground">PVEAPIToken / PBSAPIToken</span> header</span></div> <div class="flex gap-2"><span class="text-primary"></span><span class="text-muted-foreground">Token mode: <span class="text-foreground">PVEAPIToken / PBSAPIToken</span> header</span></div>
<div class="flex gap-2"><span class="text-primary"></span><span class="text-muted-foreground">Password mode: ticket auth + <span class="text-foreground">CSRFPreventionToken</span></span></div> <div class="flex gap-2"><span class="text-primary"></span><span class="text-muted-foreground">Password mode: ticket auth + <span class="text-foreground">CSRFPreventionToken</span></span></div>
<div class="flex gap-2"><span class="text-primary"></span><span class="text-muted-foreground">Fallbacks skip TOFU — intentional for home-lab failover; primary is always pinned.</span></div> <div class="flex gap-2"><span class="text-primary"></span><span class="text-muted-foreground">Fallbacks skip TOFU during home-lab failover; the primary endpoint stays pinned.</span></div>
</div> </div>
</div> </div>
@@ -24,7 +24,7 @@ import DotMatrixText from './DotMatrixText.astro'
<div class="rounded-xl border border-dotted border-white/10 bg-card p-5 shadow-card"> <div class="rounded-xl border border-dotted border-white/10 bg-card p-5 shadow-card">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="h-2 w-2 rounded-full bg-success shadow-[0_0_6px] shadow-success/60" aria-hidden="true"></span> <span class="h-2 w-2 rounded-full bg-success shadow-[0_0_6px] shadow-success/60" aria-hidden="true"></span>
<span class="font-mono text-[10px] uppercase tracking-[0.08em] text-muted-foreground">SHA-256 fingerprint pinned</span> <span class="font-mono text-[10px] uppercase tracking-[0.08em] text-muted-foreground">SHA-256 fingerprint: pinned</span>
<span class="ml-auto rounded border border-success/20 bg-success/10 px-1.5 py-0.5 font-mono text-[10px] uppercase text-success">Pinned</span> <span class="ml-auto rounded border border-success/20 bg-success/10 px-1.5 py-0.5 font-mono text-[10px] uppercase text-success">Pinned</span>
</div> </div>
<p class="mt-4 overflow-x-auto whitespace-nowrap font-mono text-[13px] tracking-[0.12em] text-foreground">A3:0F:71:C9:52:B8:4D:E0:11:2A:7F:9C:63:D4:18:0B</p> <p class="mt-4 overflow-x-auto whitespace-nowrap font-mono text-[13px] tracking-[0.12em] text-foreground">A3:0F:71:C9:52:B8:4D:E0:11:2A:7F:9C:63:D4:18:0B</p>
@@ -33,7 +33,7 @@ import DotMatrixText from './DotMatrixText.astro'
<span class="rounded bg-white/[0.04] px-2 py-1 text-muted-foreground">rustls · sha2 · x509-cert</span> <span class="rounded bg-white/[0.04] px-2 py-1 text-muted-foreground">rustls · sha2 · x509-cert</span>
<span class="rounded bg-white/[0.04] px-2 py-1 text-muted-foreground">no telemetry</span> <span class="rounded bg-white/[0.04] px-2 py-1 text-muted-foreground">no telemetry</span>
</div> </div>
<p class="mt-3 font-mono text-[10px] text-muted-foreground">Captured on first connect. Verified on every connect after. Accept anyway” per-connection when you rotate certs.</p> <p class="mt-3 font-mono text-[10px] text-muted-foreground">Clustri captures the fingerprint on first connect and checks it later. Accept a rotated certificate for one connection.</p>
</div> </div>
<div class="mt-4 grid grid-cols-3 gap-3"> <div class="mt-4 grid grid-cols-3 gap-3">
@@ -55,18 +55,18 @@ import DotMatrixText from './DotMatrixText.astro'
</div> </div>
</section> </section>
<!-- Bold statement full bleed --> <!-- Bold statement: full bleed -->
<section class="relative overflow-hidden border-y border-dotted border-white/10 bg-[#08080a]"> <section class="relative overflow-hidden border-y border-dotted border-white/10 bg-[#08080a]">
<div class="pointer-events-none absolute inset-0 grid-bg opacity-[0.22]" aria-hidden="true"></div> <div class="pointer-events-none absolute inset-0 grid-bg opacity-[0.22]" aria-hidden="true"></div>
<div class="relative mx-auto max-w-7xl px-6 py-20 sm:py-28 lg:px-8"> <div class="relative mx-auto max-w-7xl px-6 py-20 sm:py-28 lg:px-8">
<div class="max-w-4xl" data-reveal> <div class="max-w-4xl" data-reveal>
<DotMatrixText text="THE IDEA" size="xs" dotClass="text-primary" /> <DotMatrixText text="THE IDEA" size="xs" dotClass="text-primary" />
<p class="mt-6 text-3xl font-semibold leading-[0.95] tracking-[-0.04em] text-foreground sm:text-5xl lg:text-[3.4rem]"> <p class="mt-6 text-3xl font-semibold leading-[0.95] tracking-[-0.04em] text-foreground sm:text-5xl lg:text-[3.4rem]">
Proxmox is powerful.<br /> The cluster keeps running.<br />
<span class="text-muted-foreground">The web UI is not the point of failure.</span> <span class="text-muted-foreground">Your management session should stay with it.</span>
</p> </p>
<p class="mt-6 max-w-[52ch] text-[15px] leading-relaxed text-muted-foreground sm:text-[16px]"> <p class="mt-6 max-w-[52ch] text-[15px] leading-relaxed text-muted-foreground sm:text-[16px]">
Clustri sits between you and the API. When a node drops, the app retries the next one. You keep working. The cluster keeps running. Clustri sends requests through the Proxmox API. If a node stops answering, it tries the next endpoint and shows you which node answered.
</p> </p>
<div class="mt-8 h-px w-12 bg-primary/60" aria-hidden="true"></div> <div class="mt-8 h-px w-12 bg-primary/60" aria-hidden="true"></div>
</div> </div>
@@ -79,9 +79,9 @@ import DotMatrixText from './DotMatrixText.astro'
<div class="grid gap-10 lg:grid-cols-12"> <div class="grid gap-10 lg:grid-cols-12">
<div class="lg:col-span-5" data-reveal> <div class="lg:col-span-5" data-reveal>
<DotMatrixText text="CONSOLE" size="xs" dotClass="text-primary" /> <DotMatrixText text="CONSOLE" size="xs" dotClass="text-primary" />
<h2 id="console-title" class="mt-4 text-3xl font-semibold tracking-[-0.03em] text-foreground sm:text-4xl">Type like you're there.</h2> <h2 id="console-title" class="mt-4 text-3xl font-semibold tracking-[-0.03em] text-foreground sm:text-4xl">Open a console on any guest.</h2>
<p class="mt-4 max-w-[44ch] text-[15px] leading-relaxed text-muted-foreground"> <p class="mt-4 max-w-[44ch] text-[15px] leading-relaxed text-muted-foreground">
noVNC for VMs, xterm.js for containers. Fullscreen, keyboard passthrough, and Ctrl+Alt+Del where you expect it. Use noVNC for VMs and xterm.js for containers. Fullscreen mode and keyboard passthrough keep the session in the app.
</p> </p>
<div class="mt-6 inline-flex gap-2"> <div class="mt-6 inline-flex gap-2">
<span class="rounded border border-dotted border-white/10 bg-card px-2.5 py-1 font-mono text-[11px] text-muted-foreground">Fullscreen</span> <span class="rounded border border-dotted border-white/10 bg-card px-2.5 py-1 font-mono text-[11px] text-muted-foreground">Fullscreen</span>
@@ -118,10 +118,10 @@ import DotMatrixText from './DotMatrixText.astro'
<div class="flex flex-wrap items-end justify-between gap-6" data-reveal> <div class="flex flex-wrap items-end justify-between gap-6" data-reveal>
<div> <div>
<DotMatrixText text="POWER USER" size="xs" dotClass="text-primary" /> <DotMatrixText text="POWER USER" size="xs" dotClass="text-primary" />
<h2 id="power-title" class="mt-4 text-3xl font-semibold tracking-[-0.03em] text-foreground sm:text-4xl">Built for the people who keep the lights on.</h2> <h2 id="power-title" class="mt-4 text-3xl font-semibold tracking-[-0.03em] text-foreground sm:text-4xl">Controls for the work you repeat.</h2>
</div> </div>
<p class="max-w-[44ch] text-[15px] leading-relaxed text-muted-foreground"> <p class="max-w-[44ch] text-[15px] leading-relaxed text-muted-foreground">
Tasks stream over WebSocket with polling fallback. <button data-cmdk-trigger class="underline decoration-white/15 underline-offset-4 hover:text-foreground">⌘K</button> opens the palette. The tray keeps one-click actions one click away. WebSocket events update task status. If events stop, polling takes over. Press <button data-cmdk-trigger class="underline decoration-white/15 underline-offset-4 hover:text-foreground">⌘K</button> to find a command. The tray keeps common actions close.
</p> </p>
</div> </div>
@@ -132,7 +132,7 @@ import DotMatrixText from './DotMatrixText.astro'
</div> </div>
<div class="flex items-center justify-between gap-4 border-b border-dotted border-white/10 px-1 py-4 sm:px-4" data-stagger-child> <div class="flex items-center justify-between gap-4 border-b border-dotted border-white/10 px-1 py-4 sm:px-4" data-stagger-child>
<span class="font-mono text-[11px] font-medium text-primary">WebSocket</span> <span class="font-mono text-[11px] font-medium text-primary">WebSocket</span>
<span class="text-sm text-muted-foreground">Live task &amp; event stream</span> <span class="text-sm text-muted-foreground">Live task and event updates</span>
</div> </div>
<div class="flex items-center justify-between gap-4 border-b border-dotted border-white/10 px-1 py-4 sm:border-r sm:px-4 sm:border-b-0" data-stagger-child> <div class="flex items-center justify-between gap-4 border-b border-dotted border-white/10 px-1 py-4 sm:border-r sm:px-4 sm:border-b-0" data-stagger-child>
<span class="font-mono text-[11px] font-medium text-primary">Tray menu</span> <span class="font-mono text-[11px] font-medium text-primary">Tray menu</span>
+6 -6
View File
@@ -14,26 +14,26 @@ const year = new Date().getFullYear()
<DotMatrixText text="CLUSTRI" size="xs" dotClass="text-foreground" /> <DotMatrixText text="CLUSTRI" size="xs" dotClass="text-foreground" />
<span class="hidden sm:inline"><DotMatrixText text="OPS CONSOLE" size="xs" dotClass="text-muted-foreground opacity-70" /></span> <span class="hidden sm:inline"><DotMatrixText text="OPS CONSOLE" size="xs" dotClass="text-muted-foreground opacity-70" /></span>
</div> </div>
<p class="mt-3 font-mono text-[11px] text-muted-foreground">v0.1.0 · MIT · Not affiliated with Proxmox.</p> <p class="mt-3 font-mono text-[11px] text-muted-foreground">v0.1.0 · MIT · Desktop client for Proxmox.</p>
</div> </div>
<div class="flex flex-col gap-8 sm:flex-row sm:gap-16"> <div class="flex flex-col gap-8 sm:flex-row sm:gap-16">
<nav aria-label="Navigate" class="space-y-2"> <nav aria-label="Navigate" class="space-y-2">
<DotMatrixText text="NAVIGATE" size="xs" dotClass="text-muted-foreground" /> <DotMatrixText text="NAVIGATE" size="xs" dotClass="text-muted-foreground" />
<div class="mt-2 space-y-1"> <div class="mt-2 space-y-1">
<a href="#failover-playground" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">Failover demo</a> <a href="#failover-playground" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">Test failover</a>
<a href="#security" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">Security</a> <a href="#security" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">Security</a>
<a href="#tour" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">Tour</a> <a href="#tour" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">Tour</a>
<a href="#console" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">Console</a> <a href="#console" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">Console</a>
<a href="#architecture" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">Architecture</a> <a href="#architecture" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">Architecture</a>
<a href="#download" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">Download</a> <a href="#download" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">See releases</a>
</div> </div>
</nav> </nav>
<nav aria-label="Source" class="space-y-2"> <nav aria-label="Source" class="space-y-2">
<DotMatrixText text="SOURCE" size="xs" dotClass="text-muted-foreground" /> <DotMatrixText text="SOURCE" size="xs" dotClass="text-muted-foreground" />
<div class="mt-2 space-y-1"> <div class="mt-2 space-y-1">
<a href="#" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">GitHub</a> <a href="https://github.com/Factor-scd/clustri" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">GitHub</a>
<a href="#" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">Build from source</a> <a href="https://github.com/Factor-scd/clustri" class="block font-mono text-[12px] text-muted-foreground transition-colors hover:text-foreground">Build from source</a>
</div> </div>
</nav> </nav>
</div> </div>
@@ -41,7 +41,7 @@ const year = new Date().getFullYear()
<div class="mt-10 flex flex-col gap-2 border-t border-dotted border-white/10 pt-6 font-mono text-[11px] text-muted-foreground/60 md:flex-row md:items-center md:justify-between"> <div class="mt-10 flex flex-col gap-2 border-t border-dotted border-white/10 pt-6 font-mono text-[11px] text-muted-foreground/60 md:flex-row md:items-center md:justify-between">
<p>© {year} Clustri. MIT license.</p> <p>© {year} Clustri. MIT license.</p>
<p class="flex items-center gap-2">Press <kbd class="rounded border border-dotted border-white/15 bg-white/[0.04] px-1.5 py-0.5 text-[10px]">⌘K</kbd> anywhere</p> <p class="flex items-center gap-2">Press <kbd class="rounded border border-dotted border-white/15 bg-white/[0.04] px-1.5 py-0.5 text-[10px]">⌘K</kbd> to search</p>
</div> </div>
</div> </div>
</footer> </footer>
+6 -6
View File
@@ -17,17 +17,17 @@ import DotMatrixText from './DotMatrixText.astro'
<div class="relative mx-auto max-w-7xl px-6 lg:px-8"> <div class="relative mx-auto max-w-7xl px-6 lg:px-8">
<div class="pt-28 sm:pt-36 lg:pt-40"> <div class="pt-28 sm:pt-36 lg:pt-40">
<h1 class="mt-6 max-w-[14ch] text-[2.6rem] font-semibold leading-[0.92] tracking-[-0.04em] text-foreground sm:text-[3.5rem] lg:text-[4.6rem]"> <h1 class="mt-6 max-w-[14ch] text-[2.6rem] font-semibold leading-[0.92] tracking-[-0.04em] text-foreground sm:text-[3.5rem] lg:text-[4.6rem]">
Your cluster,<br /> Manage your cluster,<br />
<span class="text-muted-foreground">not your problem.</span> <span class="text-muted-foreground">from one desktop app.</span>
</h1> </h1>
<p class="mt-5 max-w-[52ch] text-[15px] leading-relaxed text-muted-foreground sm:text-[16px]"> <p class="mt-5 max-w-[52ch] text-[15px] leading-relaxed text-muted-foreground sm:text-[16px]">
Clustri is a cross-platform desktop console for Proxmox VE. Manage nodes, VMs, storage and backups and keep working when a node goes down. Clustri connects to Proxmox VE and PBS so you can manage nodes, VMs, containers, storage, backups, and consoles without keeping a browser tab open.
</p> </p>
<div class="mt-8 flex flex-wrap items-center gap-3"> <div class="mt-8 flex flex-wrap items-center gap-3">
<a href="#download" class="rounded-[10px] bg-primary px-6 py-3 text-sm font-semibold text-primary-foreground transition hover:bg-primary/90 active:scale-[0.98]">Get Clustri</a> <a href="#download" class="rounded-[10px] bg-primary px-6 py-3 text-sm font-semibold text-primary-foreground transition hover:bg-primary/90 active:scale-[0.98]">See releases</a>
<a href="#failover-playground" class="rounded-[10px] border border-dotted border-white/15 bg-white/[0.04] px-6 py-3 text-sm font-semibold text-foreground transition hover:border-white/20 hover:bg-white/[0.06] active:scale-[0.98]">Try the failover demo ↓</a> <a href="#failover-playground" class="rounded-[10px] border border-dotted border-white/15 bg-white/[0.04] px-6 py-3 text-sm font-semibold text-foreground transition hover:border-white/20 hover:bg-white/[0.06] active:scale-[0.98]">Try failover</a>
</div> </div>
<div class="mt-6 flex flex-wrap gap-x-4 gap-y-1 font-mono text-[11px] uppercase tracking-[0.08em] text-muted-foreground"> <div class="mt-6 flex flex-wrap gap-x-4 gap-y-1 font-mono text-[11px] uppercase tracking-[0.08em] text-muted-foreground">
@@ -42,7 +42,7 @@ import DotMatrixText from './DotMatrixText.astro'
<AppWindowMockup /> <AppWindowMockup />
</div> </div>
</div> </div>
<p class="mt-3 text-center font-mono text-[10px] text-muted-foreground">Live demo — values tick, tasks stream. The real app looks like this.</p> <p class="mt-3 text-center font-mono text-[10px] text-muted-foreground">Dashboard preview with live metrics and task updates.</p>
</div> </div>
</div> </div>
</section> </section>
+2 -2
View File
@@ -25,10 +25,10 @@ import DotMatrixText from './DotMatrixText.astro'
</div> </div>
<div class="flex shrink-0 items-center gap-3"> <div class="flex shrink-0 items-center gap-3">
<button data-cmdk-trigger class="hidden sm:flex items-center gap-1.5 rounded-md border border-dotted border-white/10 bg-white/[0.04] px-2.5 py-1.5 font-mono text-[11px] text-muted-foreground transition-colors hover:border-white/15 hover:text-foreground" aria-label="Open command palette"> <button data-cmdk-trigger class="hidden sm:flex items-center gap-1.5 rounded-md border border-dotted border-white/10 bg-white/[0.04] px-2.5 py-1.5 font-mono text-[11px] text-muted-foreground transition-colors hover:border-white/15 hover:text-foreground" aria-label="Open commands">
<span>⌘K</span> <span>⌘K</span>
</button> </button>
<a href="#download" class="rounded-[10px] border border-dotted border-primary/30 bg-primary px-4 py-2 text-[13px] font-semibold text-primary-foreground transition-all hover:bg-primary/90 active:scale-[0.98]">Get Clustri</a> <a href="#download" class="rounded-[10px] border border-dotted border-primary/30 bg-primary px-4 py-2 text-[13px] font-semibold text-primary-foreground transition-all hover:bg-primary/90 active:scale-[0.98]">See releases</a>
</div> </div>
</nav> </nav>
</header> </header>
+9 -9
View File
@@ -7,10 +7,10 @@ import DotMatrixText from './DotMatrixText.astro'
<div data-reveal> <div data-reveal>
<DotMatrixText text="TOUR" size="xs" dotClass="text-primary" /> <DotMatrixText text="TOUR" size="xs" dotClass="text-primary" />
<h2 id="tour-title" class="mt-4 max-w-[14ch] text-3xl font-semibold tracking-[-0.03em] text-foreground sm:text-4xl"> <h2 id="tour-title" class="mt-4 max-w-[14ch] text-3xl font-semibold tracking-[-0.03em] text-foreground sm:text-4xl">
Feels like the Proxmox UI,<br /><span class="text-muted-foreground">without the browser.</span> Manage Proxmox resources,<br /><span class="text-muted-foreground">in a desktop window.</span>
</h2> </h2>
<p class="mt-4 max-w-[48ch] text-[15px] leading-relaxed text-muted-foreground"> <p class="mt-4 max-w-[48ch] text-[15px] leading-relaxed text-muted-foreground">
Native window, keyboard-first, no electron bloat. Tabs switch instantly — the data underneath stays live via WebSocket or 10s polling. Clustri runs as a native Tauri app. Switch between VMs, consoles, and backups while WebSocket events or 10-second polling refreshes the data.
</p> </p>
</div> </div>
@@ -30,7 +30,7 @@ import DotMatrixText from './DotMatrixText.astro'
<span class="h-2.5 w-2.5 rounded-full border border-dotted border-white/10 bg-white/10"></span> <span class="h-2.5 w-2.5 rounded-full border border-dotted border-white/10 bg-white/10"></span>
<span class="h-2.5 w-2.5 rounded-full border border-dotted border-white/10 bg-white/10"></span> <span class="h-2.5 w-2.5 rounded-full border border-dotted border-white/10 bg-white/10"></span>
</div> </div>
<p id="tour-frame-label" class="font-mono text-[10px] tabular-nums text-muted-foreground">Virtual Machines 4 VMs across the cluster</p> <p id="tour-frame-label" class="font-mono text-[10px] tabular-nums text-muted-foreground">Virtual Machines: 4 VMs across the cluster</p>
<span class="hidden font-mono text-[10px] text-success sm:inline">● live</span> <span class="hidden font-mono text-[10px] text-success sm:inline">● live</span>
</div> </div>
<div class="relative bg-[#0e0e12]"> <div class="relative bg-[#0e0e12]">
@@ -41,15 +41,15 @@ import DotMatrixText from './DotMatrixText.astro'
<div class="mt-4 grid gap-3 sm:grid-cols-3"> <div class="mt-4 grid gap-3 sm:grid-cols-3">
<div class="rounded-lg border border-dotted border-white/10 bg-card p-4 dot-grid" data-reveal> <div class="rounded-lg border border-dotted border-white/10 bg-card p-4 dot-grid" data-reveal>
<DotMatrixText text="VMS + CONTAINERS" size="xs" dotClass="text-foreground" /> <DotMatrixText text="VMS + CONTAINERS" size="xs" dotClass="text-foreground" />
<p class="mt-2 text-[13px] leading-relaxed text-muted-foreground">Filter by node, type, status. Start / stop / migrate / console in one click. QEMU and LXC.</p> <p class="mt-2 text-[13px] leading-relaxed text-muted-foreground">Filter by node, type, or status. Start, stop, migrate, or open a console for QEMU and LXC guests.</p>
</div> </div>
<div class="rounded-lg border border-dotted border-white/10 bg-card p-4 dot-grid" data-reveal data-reveal-delay="80"> <div class="rounded-lg border border-dotted border-white/10 bg-card p-4 dot-grid" data-reveal data-reveal-delay="80">
<DotMatrixText text="CONSOLE" size="xs" dotClass="text-foreground" /> <DotMatrixText text="CONSOLE" size="xs" dotClass="text-foreground" />
<p class="mt-2 text-[13px] leading-relaxed text-muted-foreground">noVNC for VMs, xterm.js for CTs. Fullscreen, resize, Ctrl+Alt+Del passthrough.</p> <p class="mt-2 text-[13px] leading-relaxed text-muted-foreground">Use noVNC for VMs and xterm.js for CTs, with fullscreen, resize, and Ctrl+Alt+Del passthrough.</p>
</div> </div>
<div class="rounded-lg border border-dotted border-white/10 bg-card p-4 dot-grid" data-reveal data-reveal-delay="160"> <div class="rounded-lg border border-dotted border-white/10 bg-card p-4 dot-grid" data-reveal data-reveal-delay="160">
<DotMatrixText text="BACKUPS" size="xs" dotClass="text-foreground" /> <DotMatrixText text="BACKUPS" size="xs" dotClass="text-foreground" />
<p class="mt-1.5 text-[13px] leading-relaxed text-muted-foreground">Jobs, groups, snapshots. Verify, prune (dry-run), GC, archive download via OS dialog.</p> <p class="mt-1.5 text-[13px] leading-relaxed text-muted-foreground">Review jobs, groups, and snapshots. Verify archives, preview prune, run GC, or download through the OS dialog.</p>
</div> </div>
</div> </div>
</div> </div>
@@ -60,9 +60,9 @@ import DotMatrixText from './DotMatrixText.astro'
const label = document.getElementById('tour-frame-label') const label = document.getElementById('tour-frame-label')
const tabs = document.querySelectorAll<HTMLButtonElement>('[data-tour-tab]') const tabs = document.querySelectorAll<HTMLButtonElement>('[data-tour-tab]')
const sources: Record<string, { src: string; label: string; alt: string }> = { const sources: Record<string, { src: string; label: string; alt: string }> = {
vms: { src: '/screenshots/vms.png', label: 'Virtual Machines 4 VMs across the cluster', alt: 'Clustri VM list showing running and stopped VMs' }, vms: { src: '/screenshots/vms.png', label: 'Virtual Machines: 4 VMs across the cluster', alt: 'Clustri VM list showing running and stopped VMs' },
console: { src: '/screenshots/console.png', label: 'VNC Console web-01 (QEMU) on pve1', alt: 'Clustri console view with VNC viewer' }, console: { src: '/screenshots/console.png', label: 'VNC Console: web-01 (QEMU) on pve1', alt: 'Clustri console view with VNC viewer' },
backups: { src: '/screenshots/backups.png', label: 'Backups datastores, groups and snapshots', alt: 'Clustri backups view with datastores and prune controls' }, backups: { src: '/screenshots/backups.png', label: 'Backups: datastores, groups, and snapshots', alt: 'Clustri backups view with datastores and prune controls' },
} }
tabs.forEach((btn) => { tabs.forEach((btn) => {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
+3 -3
View File
@@ -1,18 +1,18 @@
--- ---
import DotMatrixText from './DotMatrixText.astro' import DotMatrixText from './DotMatrixText.astro'
--- ---
<section aria-label="Facts" class="border-y border-dotted border-white/10 bg-[#08080a] dot-grid"> <section aria-label="Project details" class="border-y border-dotted border-white/10 bg-[#08080a] dot-grid">
<div class="mx-auto max-w-7xl px-6 lg:px-8"> <div class="mx-auto max-w-7xl px-6 lg:px-8">
<div class="grid grid-cols-2 gap-0 py-8 sm:grid-cols-4" data-stagger> <div class="grid grid-cols-2 gap-0 py-8 sm:grid-cols-4" data-stagger>
<div class="flex flex-col items-center gap-2 border-r border-dotted border-white/10 px-2 py-6" data-stagger-child> <div class="flex flex-col items-center gap-2 border-r border-dotted border-white/10 px-2 py-6" data-stagger-child>
<span class="font-mono text-3xl font-semibold tracking-[-0.03em] tabular-nums text-foreground sm:text-4xl" data-countup="0">0</span> <span class="font-mono text-3xl font-semibold tracking-[-0.03em] tabular-nums text-foreground sm:text-4xl" data-countup="0">0</span>
<DotMatrixText text="TELEMETRY" size="xs" dotClass="text-muted-foreground" /> <DotMatrixText text="TELEMETRY" size="xs" dotClass="text-muted-foreground" />
<span class="font-mono text-[11px] text-muted-foreground">ships with the app</span> <span class="font-mono text-[11px] text-muted-foreground">telemetry disabled</span>
</div> </div>
<div class="flex flex-col items-center gap-2 border-r border-dotted border-white/10 px-2 py-6 sm:border-r" data-stagger-child> <div class="flex flex-col items-center gap-2 border-r border-dotted border-white/10 px-2 py-6 sm:border-r" data-stagger-child>
<span class="font-mono text-3xl font-semibold tracking-[-0.03em] tabular-nums text-foreground sm:text-4xl" data-countup="0">0</span> <span class="font-mono text-3xl font-semibold tracking-[-0.03em] tabular-nums text-foreground sm:text-4xl" data-countup="0">0</span>
<DotMatrixText text="CLOUD" size="xs" dotClass="text-muted-foreground" /> <DotMatrixText text="CLOUD" size="xs" dotClass="text-muted-foreground" />
<span class="font-mono text-[11px] text-muted-foreground">talks to your nodes</span> <span class="font-mono text-[11px] text-muted-foreground">runs against your infrastructure</span>
</div> </div>
<div class="flex flex-col items-center gap-2 border-r border-dotted border-white/10 px-2 py-6 max-sm:border-r-0" data-stagger-child> <div class="flex flex-col items-center gap-2 border-r border-dotted border-white/10 px-2 py-6 max-sm:border-r-0" data-stagger-child>
<span class="font-mono text-3xl font-semibold tracking-[-0.03em] tabular-nums text-primary sm:text-4xl" data-countup="64">0</span> <span class="font-mono text-3xl font-semibold tracking-[-0.03em] tabular-nums text-primary sm:text-4xl" data-countup="64">0</span>
+1 -1
View File
@@ -4,7 +4,7 @@ import '@fontsource-variable/jetbrains-mono'
import '../styles/global.css' import '../styles/global.css'
interface Props { title?: string; description?: string } interface Props { title?: string; description?: string }
const { title = 'Clustri', description = 'A desktop client for managing Proxmox VE servers and clusters.' } = Astro.props const { title = 'Clustri', description = 'A cross-platform desktop client for managing Proxmox VE and PBS servers and clusters.' } = Astro.props
--- ---
<html lang="en"> <html lang="en">
+2 -2
View File
@@ -13,8 +13,8 @@ import CommandPalette from '../components/CommandPalette.astro'
--- ---
<Base <Base
title="Clustri — Ops Console for Proxmox VE" title="Clustri | Proxmox VE desktop client"
description="Clustri is a cross-platform desktop client for managing Proxmox VE servers and clusters. Failover, security, VMs, storage, backups, and consoles in one app." description="Manage Proxmox VE and PBS from a cross-platform desktop client with failover, keyring storage, VM and container controls, backups, and consoles."
> >
<Nav /> <Nav />
<main> <main>
+11 -11
View File
@@ -159,11 +159,11 @@ function initLiveDashboard() {
const taskList = root.querySelector<HTMLElement>('[data-live-tasks]') const taskList = root.querySelector<HTMLElement>('[data-live-tasks]')
const pool = [ const pool = [
{ chip: 'RUNNING', cls: 'border-warning/30 bg-warning/10 text-warning', text: 'start VM 104', node: 'pve3', time: 'now' }, { chip: 'RUNNING', cls: 'border-warning/30 bg-warning/10 text-warning', text: 'Start VM 104', node: 'pve3', time: 'now' },
{ chip: 'OK', cls: 'border-success/25 bg-success/10 text-success', text: 'snapshot vm-100', node: 'pve1', time: 'now' }, { chip: 'OK', cls: 'border-success/25 bg-success/10 text-success', text: 'Snapshot vm-100', node: 'pve1', time: 'now' },
{ chip: 'RUNNING', cls: 'border-warning/30 bg-warning/10 text-warning', text: 'migrate VM 102 pve3', node: 'pve1 → pve3', time: 'now' }, { chip: 'RUNNING', cls: 'border-warning/30 bg-warning/10 text-warning', text: 'Move VM 102 to pve3', node: 'pve1 → pve3', time: 'now' },
{ chip: 'OK', cls: 'border-success/25 bg-success/10 text-success', text: 'backup ct-201 → pbs', node: 'pve2', time: 'now' }, { chip: 'OK', cls: 'border-success/25 bg-success/10 text-success', text: 'Back up ct-201 to PBS', node: 'pve2', time: 'now' },
{ chip: 'DONE', cls: 'border-white/10 bg-white/[0.04] text-muted-foreground', text: 'prune datastore main', node: 'pbs', time: 'now' }, { chip: 'DONE', cls: 'border-white/10 bg-white/[0.04] text-muted-foreground', text: 'Prune main datastore', node: 'pbs', time: 'now' },
] ]
let poolIdx = 0 let poolIdx = 0
@@ -221,12 +221,12 @@ function initCommandPalette() {
if (!overlay || !panel || !input || !list) return if (!overlay || !panel || !input || !list) return
const commands = [ const commands = [
{ label: 'Go to Features', desc: 'Failover, security, VMs', action: () => scrollToId('features') }, { label: 'See features', desc: 'Failover, credentials, guests', action: () => scrollToId('features') },
{ label: 'Go to Console', desc: 'noVNC · xterm.js', action: () => scrollToId('console') }, { label: 'Open console', desc: 'VM and container consoles', action: () => scrollToId('console') },
{ label: 'Go to Architecture', desc: 'Request path diagram', action: () => scrollToId('architecture') }, { label: 'See architecture', desc: 'Watch requests move', action: () => scrollToId('architecture') },
{ label: 'Go to Download', desc: 'Releases · coming soon', action: () => scrollToId('download') }, { label: 'See releases', desc: 'Packages are not published yet', action: () => scrollToId('download') },
{ label: 'Kill pve1 (demo)', desc: 'Trigger the failover playground', action: () => { scrollToId('failover-playground'); setTimeout(() => document.getElementById('fp-kill-pve1')?.click(), 600) } }, { label: 'Stop pve1 (demo)', desc: 'Stop pve1 and watch failover', action: () => { scrollToId('failover-playground'); setTimeout(() => document.getElementById('fp-kill-pve1')?.click(), 600) } },
{ label: 'Copy install command', desc: 'npm run tauri dev', action: () => navigator.clipboard.writeText('npm run tauri dev') }, { label: 'Copy install command', desc: 'Copy npm run tauri dev', action: () => navigator.clipboard.writeText('npm run tauri dev') },
] ]
let filtered = [...commands] let filtered = [...commands]
+248 -5
View File
@@ -58,6 +58,15 @@ dependencies = [
"security-framework", "security-framework",
] ]
[[package]]
name = "arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
dependencies = [
"derive_arbitrary",
]
[[package]] [[package]]
name = "assert-json-diff" name = "assert-json-diff"
version = "2.0.2" version = "2.0.2"
@@ -445,6 +454,7 @@ dependencies = [
"tauri", "tauri",
"tauri-build", "tauri-build",
"tauri-plugin-dialog", "tauri-plugin-dialog",
"tauri-plugin-updater",
"tempfile", "tempfile",
"thiserror 2.0.19", "thiserror 2.0.19",
"tokio", "tokio",
@@ -706,6 +716,17 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "derive_arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "2.1.1" version = "2.1.1"
@@ -962,6 +983,16 @@ dependencies = [
"rustc_version", "rustc_version",
] ]
[[package]]
name = "filetime"
version = "0.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
dependencies = [
"cfg-if",
"libc",
]
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.9" version = "0.1.9"
@@ -1860,6 +1891,36 @@ dependencies = [
"windows-sys 0.45.0", "windows-sys 0.45.0",
] ]
[[package]]
name = "jni"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
dependencies = [
"cfg-if",
"combine",
"jni-macros",
"jni-sys 0.4.1",
"log",
"simd_cesu8",
"thiserror 2.0.19",
"walkdir",
"windows-link 0.2.1",
]
[[package]]
name = "jni-macros"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"simd_cesu8",
"syn 2.0.119",
]
[[package]] [[package]]
name = "jni-sys" name = "jni-sys"
version = "0.3.1" version = "0.3.1"
@@ -2094,6 +2155,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minisign-verify"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.9" version = "0.8.9"
@@ -2342,6 +2409,18 @@ dependencies = [
"objc2-core-foundation", "objc2-core-foundation",
] ]
[[package]]
name = "objc2-osa-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
dependencies = [
"bitflags 2.13.1",
"objc2",
"objc2-app-kit",
"objc2-foundation",
]
[[package]] [[package]]
name = "objc2-quartz-core" name = "objc2-quartz-core"
version = "0.3.2" version = "0.3.2"
@@ -2405,12 +2484,32 @@ version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]] [[package]]
name = "option-ext" name = "option-ext"
version = "0.2.0" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "osakit"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
dependencies = [
"objc2",
"objc2-foundation",
"objc2-osa-kit",
"serde",
"serde_json",
"thiserror 2.0.19",
]
[[package]] [[package]]
name = "pango" name = "pango"
version = "0.18.3" version = "0.18.3"
@@ -2977,15 +3076,20 @@ dependencies = [
"http-body", "http-body",
"http-body-util", "http-body-util",
"hyper", "hyper",
"hyper-rustls",
"hyper-util", "hyper-util",
"js-sys", "js-sys",
"log", "log",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
"serde", "serde",
"serde_json", "serde_json",
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-rustls",
"tokio-util", "tokio-util",
"tower", "tower",
"tower-http", "tower-http",
@@ -3079,6 +3183,18 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rustls-native-certs"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]] [[package]]
name = "rustls-pemfile" name = "rustls-pemfile"
version = "2.2.0" version = "2.2.0"
@@ -3098,6 +3214,33 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rustls-platform-verifier"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
dependencies = [
"core-foundation",
"core-foundation-sys",
"jni 0.22.4",
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
"security-framework",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.103.13" version = "0.103.13"
@@ -3131,6 +3274,15 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "schannel"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
dependencies = [
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "schemars" name = "schemars"
version = "0.8.22" version = "0.8.22"
@@ -3464,6 +3616,22 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
[[package]]
name = "simd_cesu8"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
dependencies = [
"rustc_version",
"simdutf8",
]
[[package]]
name = "simdutf8"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]] [[package]]
name = "similar" name = "similar"
version = "2.7.0" version = "2.7.0"
@@ -3707,7 +3875,7 @@ dependencies = [
"gdkwayland-sys", "gdkwayland-sys",
"gdkx11-sys", "gdkx11-sys",
"gtk", "gtk",
"jni", "jni 0.21.1",
"libc", "libc",
"log", "log",
"ndk", "ndk",
@@ -3740,6 +3908,17 @@ dependencies = [
"syn 2.0.119", "syn 2.0.119",
] ]
[[package]]
name = "tar"
version = "0.4.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]] [[package]]
name = "target-lexicon" name = "target-lexicon"
version = "0.12.16" version = "0.12.16"
@@ -3763,7 +3942,7 @@ dependencies = [
"gtk", "gtk",
"heck 0.5.0", "heck 0.5.0",
"http", "http",
"jni", "jni 0.21.1",
"libc", "libc",
"log", "log",
"mime", "mime",
@@ -3917,6 +4096,39 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "tauri-plugin-updater"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af"
dependencies = [
"base64 0.22.1",
"dirs",
"flate2",
"futures-util",
"http",
"infer",
"log",
"minisign-verify",
"osakit",
"percent-encoding",
"reqwest 0.13.4",
"rustls",
"semver",
"serde",
"serde_json",
"tar",
"tauri",
"tauri-plugin",
"tempfile",
"thiserror 2.0.19",
"time",
"tokio",
"url",
"windows-sys 0.60.2",
"zip",
]
[[package]] [[package]]
name = "tauri-runtime" name = "tauri-runtime"
version = "2.11.3" version = "2.11.3"
@@ -3927,7 +4139,7 @@ dependencies = [
"dpi", "dpi",
"gtk", "gtk",
"http", "http",
"jni", "jni 0.21.1",
"objc2", "objc2",
"objc2-ui-kit", "objc2-ui-kit",
"objc2-web-kit", "objc2-web-kit",
@@ -3950,7 +4162,7 @@ checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f"
dependencies = [ dependencies = [
"gtk", "gtk",
"http", "http",
"jni", "jni 0.21.1",
"log", "log",
"objc2", "objc2",
"objc2-app-kit", "objc2-app-kit",
@@ -4804,6 +5016,15 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "webpki-root-certs"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
dependencies = [
"rustls-pki-types",
]
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "0.26.11" version = "0.26.11"
@@ -5369,7 +5590,7 @@ dependencies = [
"gtk", "gtk",
"http", "http",
"javascriptcore-rs", "javascriptcore-rs",
"jni", "jni 0.21.1",
"libc", "libc",
"ndk", "ndk",
"objc2", "objc2",
@@ -5428,6 +5649,16 @@ dependencies = [
"tls_codec", "tls_codec",
] ]
[[package]]
name = "xattr"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [
"libc",
"rustix",
]
[[package]] [[package]]
name = "yasna" name = "yasna"
version = "0.5.2" version = "0.5.2"
@@ -5554,6 +5785,18 @@ dependencies = [
"syn 2.0.119", "syn 2.0.119",
] ]
[[package]]
name = "zip"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
dependencies = [
"arbitrary",
"crc32fast",
"indexmap 2.14.0",
"memchr",
]
[[package]] [[package]]
name = "zmij" name = "zmij"
version = "1.0.23" version = "1.0.23"
+1
View File
@@ -11,6 +11,7 @@ tauri-build = { version = "2", features = [] }
[dependencies] [dependencies]
tauri = { version = "2", features = ["tray-icon"] } tauri = { version = "2", features = ["tray-icon"] }
tauri-plugin-dialog = "2" tauri-plugin-dialog = "2"
tauri-plugin-updater = "2"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }
File diff suppressed because one or more lines are too long
+54
View File
@@ -2257,6 +2257,60 @@
"type": "string", "type": "string",
"const": "dialog:deny-save", "const": "dialog:deny-save",
"markdownDescription": "Denies the save command without any pre-configured scope." "markdownDescription": "Denies the save command without any pre-configured scope."
},
{
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
"type": "string",
"const": "updater:default",
"markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
},
{
"description": "Enables the check command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-check",
"markdownDescription": "Enables the check command without any pre-configured scope."
},
{
"description": "Enables the download command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-download",
"markdownDescription": "Enables the download command without any pre-configured scope."
},
{
"description": "Enables the download_and_install command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-download-and-install",
"markdownDescription": "Enables the download_and_install command without any pre-configured scope."
},
{
"description": "Enables the install command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-install",
"markdownDescription": "Enables the install command without any pre-configured scope."
},
{
"description": "Denies the check command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-check",
"markdownDescription": "Denies the check command without any pre-configured scope."
},
{
"description": "Denies the download command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-download",
"markdownDescription": "Denies the download command without any pre-configured scope."
},
{
"description": "Denies the download_and_install command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-download-and-install",
"markdownDescription": "Denies the download_and_install command without any pre-configured scope."
},
{
"description": "Denies the install command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-install",
"markdownDescription": "Denies the install command without any pre-configured scope."
} }
] ]
}, },
+54
View File
@@ -2257,6 +2257,60 @@
"type": "string", "type": "string",
"const": "dialog:deny-save", "const": "dialog:deny-save",
"markdownDescription": "Denies the save command without any pre-configured scope." "markdownDescription": "Denies the save command without any pre-configured scope."
},
{
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
"type": "string",
"const": "updater:default",
"markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
},
{
"description": "Enables the check command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-check",
"markdownDescription": "Enables the check command without any pre-configured scope."
},
{
"description": "Enables the download command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-download",
"markdownDescription": "Enables the download command without any pre-configured scope."
},
{
"description": "Enables the download_and_install command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-download-and-install",
"markdownDescription": "Enables the download_and_install command without any pre-configured scope."
},
{
"description": "Enables the install command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-install",
"markdownDescription": "Enables the install command without any pre-configured scope."
},
{
"description": "Denies the check command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-check",
"markdownDescription": "Denies the check command without any pre-configured scope."
},
{
"description": "Denies the download command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-download",
"markdownDescription": "Denies the download command without any pre-configured scope."
},
{
"description": "Denies the download_and_install command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-download-and-install",
"markdownDescription": "Denies the download_and_install command without any pre-configured scope."
},
{
"description": "Denies the install command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-install",
"markdownDescription": "Denies the install command without any pre-configured scope."
} }
] ]
}, },
+1
View File
@@ -1249,6 +1249,7 @@ pub fn run() {
update_tray_menu, update_tray_menu,
]) ])
.plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.setup(|app| { .setup(|app| {
// Build the system tray menu // Build the system tray menu
let show_hide = MenuItemBuilder::new("Show / Hide") let show_hide = MenuItemBuilder::new("Show / Hide")
+1 -1
View File
@@ -1 +1 @@
{"rustc_fingerprint":6524788087311550793,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.95.0 (59807616e 2026-04-14)\nbinary: rustc\ncommit-hash: 59807616e1fa2540724bfbac14d7976d7e4a3860\ncommit-date: 2026-04-14\nhost: aarch64-apple-darwin\nrelease: 1.95.0\nLLVM version: 22.1.2\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/matt/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""}},"successes":{}} {"rustc_fingerprint":9200552825474144316,"outputs":{"7752308220390268658":{"success":true,"status":"","code":0,"stdout":"rustc 1.97.1 (8bab26f4f 2026-07-14)\nbinary: rustc\ncommit-hash: 8bab26f4f68e0e26f0bb7960be334d5b520ea452\ncommit-date: 2026-07-14\nhost: x86_64-unknown-linux-gnu\nrelease: 1.97.1\nLLVM version: 22.1.6\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_primitive_alignment=\"16\"\ntarget_has_atomic_primitive_alignment=\"32\"\ntarget_has_atomic_primitive_alignment=\"64\"\ntarget_has_atomic_primitive_alignment=\"8\"\ntarget_has_atomic_primitive_alignment=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}}
+8
View File
@@ -34,5 +34,13 @@
"icons/icon.icns", "icons/icon.icns",
"icons/icon.ico" "icons/icon.ico"
] ]
},
"plugins": {
"updater": {
"enabled": true,
"endpoints": [
"https://github.com/Factor-scd/clustri/releases/latest/download/latest.json"
]
}
} }
} }