Skip to content

Commit 948078a

Browse files
author
Mohammod Al Amin Ashik
committed
feat: implement Tauri updater functionality
- Add UpdateChecker component with full UI/UX for checking and installing updates - Add SettingsPage integrating updater, theme, and logs functionality - Initialize tauri-plugin-updater in Rust backend (lib.rs) - Add auto-check for updates on app startup (5 second delay) - Install @tauri-apps/plugin-updater and plugin-process dependencies - Enhance E2E tests with 11 new test cases for updater functionality - Add Tauri plugin mocks to test setup Fixes runtime 'plugin updater not found' error by registering plugin in Builder chain
1 parent 9dfb59f commit 948078a

11 files changed

Lines changed: 670 additions & 141 deletions

File tree

.vscode/settings.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
2-
"[rust]": {
3-
"editor.formatOnSave": true,
4-
"editor.defaultFormatter": "rust-lang.rust-analyzer"
5-
},
6-
"rust-analyzer.rustfmt.extraArgs": [
7-
"+nightly"
8-
],
9-
"files.eol": "\n",
10-
"files.insertFinalNewline": true,
11-
"files.trimTrailingWhitespace": true,
12-
"editor.formatOnSave": true
2+
"[rust]": {
3+
"editor.formatOnSave": true,
4+
"editor.defaultFormatter": "rust-lang.rust-analyzer"
5+
},
6+
"rust-analyzer.rustfmt.extraArgs": [
7+
"+nightly"
8+
],
9+
"files.eol": "\n",
10+
"files.insertFinalNewline": true,
11+
"files.trimTrailingWhitespace": true,
12+
"editor.formatOnSave": true
1313
}

Cargo.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/desktop/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
"@monaco-editor/react": "^4.7.0",
2020
"@tauri-apps/api": "^2.10.1",
2121
"@tauri-apps/plugin-opener": "^2",
22+
"@tauri-apps/plugin-process": "^2",
23+
"@tauri-apps/plugin-updater": "^2",
2224
"immer": "^11.0.1",
2325
"lucide-react": "^0.561.0",
2426
"react": "^19.1.0",

apps/desktop/src-tauri/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ pub fn run() {
189189
tauri::Builder::default()
190190
.plugin(tauri_plugin_opener::init())
191191
.plugin(tauri_plugin_deep_link::init())
192+
.plugin(tauri_plugin_updater::Builder::new().build())
192193
.plugin(tauri_plugin_single_instance::init(|app, args, cwd| {
193194
// This callback is called when a second instance is launched
194195
info!("Second instance detected, focusing existing window");

apps/desktop/src/App.tsx

Lines changed: 21 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { FeatureSetsPage } from '@/features/featuresets';
3636
import { ClientsPage } from '@/features/clients';
3737
import { ServersPage } from '@/features/servers';
3838
import { SpacesPage } from '@/features/spaces';
39+
import { SettingsPage } from '@/features/settings';
3940
import { useGatewayEvents, useServerStatusEvents } from '@/hooks/useDomainEvents';
4041

4142
type NavItem = 'home' | 'registry' | 'servers' | 'spaces' | 'featuresets' | 'clients' | 'settings';
@@ -46,6 +47,25 @@ function AppContent() {
4647

4748
const [activeNav, setActiveNav] = useState<NavItem>('home');
4849

50+
// Auto-check for updates on startup (silent check after 5 seconds)
51+
useEffect(() => {
52+
const checkForUpdates = async () => {
53+
try {
54+
const { check } = await import('@tauri-apps/plugin-updater');
55+
const update = await check();
56+
if (update) {
57+
console.log(`[Auto-Update] Update available: ${update.version}`);
58+
// User can check Settings page to see the update
59+
}
60+
} catch (error) {
61+
console.error('[Auto-Update] Failed to check for updates:', error);
62+
}
63+
};
64+
65+
const timer = setTimeout(checkForUpdates, 5000);
66+
return () => clearTimeout(timer);
67+
}, []);
68+
4969
// Get state from store
5070
const theme = useTheme();
5171
const setTheme = useAppStore((state) => state.setTheme);
@@ -171,7 +191,7 @@ function AppContent() {
171191
{activeNav === 'spaces' && <SpacesPage />}
172192
{activeNav === 'featuresets' && <FeatureSetsPage />}
173193
{activeNav === 'clients' && <ClientsPage />}
174-
{activeNav === 'settings' && <SettingsView />}
194+
{activeNav === 'settings' && <SettingsPage />}
175195
</div>
176196
</AppShell>
177197
);
@@ -425,128 +445,4 @@ function DashboardView() {
425445
);
426446
}
427447

428-
function SettingsView() {
429-
const theme = useTheme();
430-
const setTheme = useAppStore((state) => state.setTheme);
431-
const [logsPath, setLogsPath] = useState<string>('');
432-
const [openingLogs, setOpeningLogs] = useState(false);
433-
434-
// Load logs path on mount
435-
useEffect(() => {
436-
const loadLogsPath = async () => {
437-
try {
438-
const { invoke } = await import('@tauri-apps/api/core');
439-
const path = await invoke<string>('get_logs_path');
440-
setLogsPath(path);
441-
} catch (error) {
442-
console.error('Failed to get logs path:', error);
443-
}
444-
};
445-
loadLogsPath();
446-
}, []);
447-
448-
const handleOpenLogs = async () => {
449-
setOpeningLogs(true);
450-
try {
451-
const { invoke } = await import('@tauri-apps/api/core');
452-
await invoke('open_logs_folder');
453-
} catch (error) {
454-
console.error('Failed to open logs folder:', error);
455-
} finally {
456-
setOpeningLogs(false);
457-
}
458-
};
459-
460-
return (
461-
<div className="space-y-6">
462-
<div>
463-
<h1 className="text-2xl font-bold">Settings</h1>
464-
<p className="text-[rgb(var(--muted))]">Configure McpMux preferences.</p>
465-
</div>
466-
467-
<Card>
468-
<CardHeader>
469-
<CardTitle>Appearance</CardTitle>
470-
<CardDescription>Customize the look and feel of McpMux.</CardDescription>
471-
</CardHeader>
472-
<CardContent>
473-
<div className="space-y-4">
474-
<div>
475-
<label className="text-sm font-medium">Theme</label>
476-
<div className="flex gap-2 mt-2" data-testid="theme-buttons">
477-
<Button
478-
variant={theme === 'light' ? 'primary' : 'secondary'}
479-
size="sm"
480-
onClick={() => setTheme('light')}
481-
data-testid="theme-light-btn"
482-
>
483-
<Sun className="h-4 w-4 mr-2" />
484-
Light
485-
</Button>
486-
<Button
487-
variant={theme === 'dark' ? 'primary' : 'secondary'}
488-
size="sm"
489-
onClick={() => setTheme('dark')}
490-
data-testid="theme-dark-btn"
491-
>
492-
<Moon className="h-4 w-4 mr-2" />
493-
Dark
494-
</Button>
495-
<Button
496-
variant={theme === 'system' ? 'primary' : 'secondary'}
497-
size="sm"
498-
onClick={() => setTheme('system')}
499-
data-testid="theme-system-btn"
500-
>
501-
<Monitor className="h-4 w-4 mr-2" />
502-
System
503-
</Button>
504-
</div>
505-
</div>
506-
</div>
507-
</CardContent>
508-
</Card>
509-
510-
<Card>
511-
<CardHeader>
512-
<CardTitle className="flex items-center gap-2">
513-
<FileText className="h-5 w-5" />
514-
Logs
515-
</CardTitle>
516-
<CardDescription>View application logs for debugging and troubleshooting.</CardDescription>
517-
</CardHeader>
518-
<CardContent>
519-
<div className="space-y-4">
520-
<div>
521-
<label className="text-sm font-medium">Log Files Location</label>
522-
<p className="text-sm text-[rgb(var(--muted))] mt-1 font-mono bg-surface-secondary rounded px-2 py-1" data-testid="logs-path">
523-
{logsPath || 'Loading...'}
524-
</p>
525-
</div>
526-
<div className="flex items-center gap-2">
527-
<Button
528-
variant="secondary"
529-
size="sm"
530-
onClick={handleOpenLogs}
531-
disabled={openingLogs}
532-
data-testid="open-logs-btn"
533-
>
534-
{openingLogs ? (
535-
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
536-
) : (
537-
<FolderOpen className="h-4 w-4 mr-2" />
538-
)}
539-
Open Logs Folder
540-
</Button>
541-
</div>
542-
<p className="text-xs text-[rgb(var(--muted))]">
543-
Logs are rotated daily. Each file contains detailed debug information including thread IDs and source locations.
544-
</p>
545-
</div>
546-
</CardContent>
547-
</Card>
548-
</div>
549-
);
550-
}
551-
552448
export default App;

0 commit comments

Comments
 (0)