Compare commits
No commits in common. "8b14f7faaa4fa0eb9f52edf3afae0be48a6f4ad9" and "3d10093ddfda1b5d55d2b6bea69d694ce8dfa207" have entirely different histories.
8b14f7faaa
...
3d10093ddf
@ -1,315 +0,0 @@
|
||||
# Proxmox Integration - v1.2.1 Fixes Plan
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This plan addresses 4 critical issues reported against the Proxmox integration and ensures proper feature parity with Proxmox Datacenter Manager (PDM) while maintaining **MIT license compliance**.
|
||||
|
||||
**Version:** v1.2.1
|
||||
**Branch:** `fix/proxmox-v1.2.1`
|
||||
**Status:** Planning Phase
|
||||
|
||||
---
|
||||
|
||||
## Critical Issues to Fix
|
||||
|
||||
### Issue 1: Auto-updater for tftsr Application ❌
|
||||
**Problem:** Auto-updater for the tftsr application itself is missing. User wants to pull from latest Stable and Pre-Release builds.
|
||||
|
||||
**Root Cause:**
|
||||
- PDM does NOT have an application auto-updater (it's a server-side app)
|
||||
- tftsr has NO auto-updater implementation
|
||||
- No Tauri updater plugin installed
|
||||
- No updater UI in Settings section
|
||||
|
||||
**Tauri Auto-Updater Requirements:**
|
||||
- `tauri-plugin-updater = "2"` dependency
|
||||
- Updater configuration in `tauri.conf.json`
|
||||
- Settings UI for channel selection (Stable/Pre-Release)
|
||||
- Backend commands for checking/updating
|
||||
|
||||
**Solution:**
|
||||
1. Add `tauri-plugin-updater` to Cargo.toml
|
||||
2. Configure updater in tauri.conf.json with Gitea releases endpoint
|
||||
3. Create Settings UI page for updater configuration
|
||||
4. Add channel selector (Stable vs Pre-Release)
|
||||
5. Implement check/update commands
|
||||
|
||||
**Files to Modify:**
|
||||
- `src-tauri/Cargo.toml` - add updater plugin, **bump version to 1.2.1**
|
||||
- `src-tauri/tauri.conf.json` - add updater config, **bump version to 1.2.1**
|
||||
- `src/App.tsx` - add updater route
|
||||
- `src/pages/Settings/` - add updater UI
|
||||
- `src-tauri/src/commands/system.rs` - add updater commands
|
||||
|
||||
**Gitea Release API:**
|
||||
- Stable: `https://gogs.tftsr.com/api/v1/repos/sarman/tftsr-devops_investigation/releases?latest=true`
|
||||
- Pre-Release: `https://gogs.tftsr.com/api/v1/repos/sarman/tftsr-devops_investigation/releases?prerelease=true`
|
||||
|
||||
### Issue 2: Dummy Data Preloaded ❌
|
||||
**Problem:** Dummy Proxmox connection data is preloaded in the UI
|
||||
|
||||
**Root Cause:** `src/pages/Proxmox/RemotesPage.tsx` lines 21-22 contain hardcoded dummy data:
|
||||
```typescript
|
||||
const [remotes, setRemotes] = useState<RemoteInfo[]>([
|
||||
{ id: '1', name: 'Production Cluster', url: 'https://pve1.example.com:8006', ... },
|
||||
{ id: '2', name: 'Backup Server', url: 'https://pbs1.example.com:8007', ... },
|
||||
]);
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
- Change initial state to empty array: `useState<RemoteInfo[]>([])`
|
||||
- Fetch real data from backend via `list_proxmox_clusters` command
|
||||
- Display empty state UI when no remotes configured
|
||||
|
||||
**Files to Modify:**
|
||||
- `src/pages/Proxmox/RemotesPage.tsx` - remove hardcoded data
|
||||
|
||||
### Issue 3: Cannot Create/Save Proxmox Connections ❌
|
||||
**Problem:** User cannot create and save Proxmox connections to test functionality
|
||||
|
||||
**Root Cause:** Frontend uses React component state instead of calling backend API commands. The `handleAddRemote` and `handleEditRemote` functions only update local state, not the database.
|
||||
|
||||
**Solution:**
|
||||
1. Add missing Tauri command wrappers (see Issue 4)
|
||||
2. Update frontend to call backend commands:
|
||||
- `add_proxmox_cluster` - for creating new connections
|
||||
- `remove_proxmox_cluster` - for deleting connections
|
||||
- `list_proxmox_clusters` - for fetching existing connections
|
||||
|
||||
**Files to Modify:**
|
||||
- `src/pages/Proxmox/RemotesPage.tsx` - wire up backend commands
|
||||
- `src-tauri/src/commands/proxmox.rs` - verify all commands exist
|
||||
|
||||
### Issue 4: Proxmox Section Collapsed by Default ⚠️
|
||||
**Problem:** Proxmox section should be collapsed by default in sidebar
|
||||
|
||||
**Solution:**
|
||||
- Modify `src/App.tsx` sidebar configuration
|
||||
- Set Proxmox menu item to collapsed state by default
|
||||
- User can expand as needed
|
||||
|
||||
**Files to Modify:**
|
||||
- `src/App.tsx` - adjust sidebar default state
|
||||
|
||||
---
|
||||
|
||||
## PDM Feature Parity Analysis
|
||||
|
||||
### What PDM Actually Has (from `/home/sarman/Documents/proxmox-datacenter-manager`)
|
||||
|
||||
#### ✅ Implemented in tftsr (Feature Parity Achieved)
|
||||
1. **Dashboard Widgets** - 11 widget types
|
||||
2. **Resource Tree View** - Hierarchical browser
|
||||
3. **VM Manager UI** - Start/stop/reboot/shutdown
|
||||
4. **Ceph Manager UI** - Pools, OSDs, monitors
|
||||
5. **SDN Manager UI** - EVPN zones, virtual networks
|
||||
6. **Firewall Manager UI** - Rule management
|
||||
7. **HA Groups UI** - Group management
|
||||
8. **User Management UI** - Realm management
|
||||
9. **Certificate Manager UI** - Certificate management
|
||||
10. **Subscription Registry UI** - Subscription keys
|
||||
11. **Search Functionality** - Global search (Ctrl+Space)
|
||||
12. **Notes System** - Remote notes
|
||||
13. **Remote Shell** - Terminal access
|
||||
14. **Task Management** - Task list and status
|
||||
|
||||
#### ❌ Missing in tftsr (Backend Commands Only)
|
||||
1. **VM Operations** - Create, clone, delete VMs
|
||||
2. **VM Snapshots** - Create, rollback, delete snapshots
|
||||
3. **Backup Job Management** - Create, edit, delete jobs
|
||||
4. **Ceph Pool Management** - Create, delete, quota management
|
||||
5. **Ceph OSD Management** - Set weight, mark in/out, zap
|
||||
6. **Firewall Rule Management** - Move up/down, enable/disable
|
||||
7. **HA Group Management** - Create, edit, delete groups
|
||||
8. **Auth Realm Management** - Create, edit, delete realms
|
||||
9. **Certificate Management** - Upload, delete, renew
|
||||
10. **Subscription Management** - Add, remove subscription keys
|
||||
|
||||
#### ⚠️ Documentation Issues
|
||||
1. **"Phase 8-17" claimed as complete** but no IPC interface
|
||||
2. **"100% feature parity"** overstated - missing backend commands
|
||||
3. **UI components exist** but no backend integration
|
||||
|
||||
---
|
||||
|
||||
## MIT Compliance Requirements
|
||||
|
||||
### CRITICAL: Do NOT Copy PDM Code
|
||||
|
||||
**PDM License:** AGPL-3 (NOT MIT compatible)
|
||||
|
||||
**What We CAN Do:**
|
||||
- ✅ Use PDM API documentation as reference
|
||||
- ✅ Implement features from scratch
|
||||
- ✅ Follow tftsr's architecture patterns
|
||||
- ✅ Use official Proxmox VE/PBS API docs
|
||||
|
||||
**What We CANNOT Do:**
|
||||
- ❌ Copy PDM source code directly
|
||||
- ❌ Use PDM UI components
|
||||
- ❌ Use PDM Rust modules
|
||||
- ❌ Reference PDM code in implementation
|
||||
|
||||
**Implementation Strategy:**
|
||||
1. Review PDM's API endpoints in `server/src/api/`
|
||||
2. Implement equivalent Rust backend commands in tftsr
|
||||
3. Create React/TypeScript UI components in tftsr
|
||||
4. Ensure all code follows tftsr's existing patterns
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Critical Fixes (High Priority)
|
||||
**Time Estimate:** 3-4 hours
|
||||
|
||||
1. **Create new branch** `fix/proxmox-v1.2.1`
|
||||
2. **Add tauri-plugin-updater** - Enable auto-updater for tftsr
|
||||
3. **Configure updater in tauri.conf.json** - Gitea releases endpoint
|
||||
4. **Create Settings UI page** - Channel selector (Stable/Pre-Release)
|
||||
5. **Add updater backend commands** - Check, download, install updates
|
||||
6. **Remove dummy data** from `RemotesPage.tsx`
|
||||
7. **Fix connection save** - wire frontend to backend commands
|
||||
8. **Add Proxmox collapsed** to sidebar default
|
||||
9. **Update CHANGELOG.md** to v1.2.1
|
||||
|
||||
### Phase 2: Backend Command Wrappers (Medium Priority)
|
||||
**Time Estimate:** 3-4 hours
|
||||
|
||||
11. **Add missing Tauri commands** for:
|
||||
- VM operations (create, clone, delete)
|
||||
- Snapshot operations (create, rollback, delete)
|
||||
- Backup job management (create, edit, delete)
|
||||
- Ceph pool management (create, delete, quota)
|
||||
- Ceph OSD management (weight, in/out, zap)
|
||||
- Firewall rules (move, enable/disable)
|
||||
- HA groups (create, edit, delete)
|
||||
- Auth realms (create, edit, delete)
|
||||
- Certificates (upload, delete, renew)
|
||||
- Subscriptions (add, remove keys)
|
||||
|
||||
### Phase 3: Documentation Updates (Medium Priority)
|
||||
**Time Estimate:** 1-2 hours
|
||||
|
||||
12. **Update feature parity docs** - correct "100% complete" claims
|
||||
13. **Update implementation summary** - reflect actual status
|
||||
14. **Add missing features** section
|
||||
15. **Remove outdated phase claims**
|
||||
|
||||
### Phase 4: Verification (High Priority)
|
||||
**Time Estimate:** 1-2 hours
|
||||
|
||||
16. **Run cargo fmt** - format Rust code
|
||||
17. **Run cargo clippy** - fix warnings
|
||||
18. **Run cargo test** - verify all tests pass
|
||||
19. **Run npm run build** - verify frontend builds
|
||||
20. **Run npm run test** - verify frontend tests pass
|
||||
|
||||
---
|
||||
|
||||
## Files to Modify
|
||||
|
||||
### Critical (Phase 1)
|
||||
1. `src-tauri/Cargo.toml` - Add tauri-plugin-updater, update version to 1.2.1
|
||||
2. `src-tauri/tauri.conf.json` - Add updater config, update version to 1.2.1
|
||||
3. `src/pages/Proxmox/RemotesPage.tsx` - Remove dummy data, add backend integration
|
||||
4. `src/App.tsx` - Make Proxmox section collapsed by default, add updater route
|
||||
5. `CHANGELOG.md` - Add v1.2.1 entry
|
||||
6. `src-tauri/src/commands/system.rs` - Add updater commands
|
||||
7. `src/pages/Settings/` - Create updater settings page
|
||||
|
||||
### Backend (Phase 2)
|
||||
8. `src-tauri/src/commands/proxmox.rs` - Add missing command wrappers
|
||||
9. `src-tauri/src/proxmox/*.rs` - Verify backend functions exist
|
||||
|
||||
### Documentation (Phase 3)
|
||||
8. `docs/PROXMOX-FEATURE-PARITY-STATUS.md` - Correct phase claims
|
||||
9. `docs/PROXMOX-COMPLETE.md` - Update status
|
||||
10. `docs/PROXMOX-IMPLEMENTATION-SUMMARY.md` - Update implementation status
|
||||
11. `docs/wiki/Proxmox-Management.md` - Add if missing
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Manual Testing Checklist
|
||||
- [ ] Create new Proxmox VE connection
|
||||
- [ ] Create new Proxmox PBS connection
|
||||
- [ ] Edit existing connection
|
||||
- [ ] Delete connection
|
||||
- [ ] List all connections
|
||||
- [ ] Verify no dummy data on page load
|
||||
- [ ] Verify Proxmox section collapsed by default
|
||||
- [ ] Test VM operations (start/stop/reboot/shutdown)
|
||||
- [ ] Test Ceph pool operations
|
||||
- [ ] Test SDN zone operations
|
||||
- [ ] Test firewall rule operations
|
||||
|
||||
### Automated Testing
|
||||
- [ ] Rust unit tests pass (64 tests)
|
||||
- [ ] Rust clippy passes (0 warnings)
|
||||
- [ ] Rust format check passes
|
||||
- [ ] TypeScript compilation passes
|
||||
- [ ] ESLint passes (0 errors)
|
||||
- [ ] Frontend unit tests pass (13 tests)
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Issue Resolution
|
||||
- [ ] No dummy data preloaded in Proxmox remotes
|
||||
- [ ] User can create, edit, delete Proxmox connections
|
||||
- [ ] Connections saved to database (not just React state)
|
||||
- [ ] Proxmox section collapsed by default in sidebar
|
||||
- [ ] Auto-updater implemented with Stable/Pre-Release channel selection
|
||||
- [ ] Updates download from Gitea releases API
|
||||
|
||||
### Feature Parity
|
||||
- [ ] All documented PDM features implemented
|
||||
- [ ] Backend command wrappers for all UI components
|
||||
- [ ] Frontend calls backend commands correctly
|
||||
- [ ] Documentation reflects actual implementation status
|
||||
|
||||
### Code Quality
|
||||
- [ ] 0 clippy warnings
|
||||
- [ ] 0 test failures
|
||||
- [ ] 0 TypeScript errors
|
||||
- [ ] 0 ESLint errors
|
||||
- [ ] MIT license compliance verified
|
||||
|
||||
---
|
||||
|
||||
## Risks & Mitigations
|
||||
|
||||
### Risk 1: Backend Commands Missing
|
||||
**Mitigation:** Verify all backend functions exist in `src-tauri/src/proxmox/` before adding command wrappers.
|
||||
|
||||
### Risk 2: Frontend Integration Breaking
|
||||
**Mitigation:** Test each change incrementally, commit after each working change.
|
||||
|
||||
### Risk 3: Documentation Inconsistency
|
||||
**Mitigation:** Update all docs in single commit to ensure consistency.
|
||||
|
||||
### Risk 4: MIT Compliance Violation
|
||||
**Mitigation:** Never copy PDM source code. Use only API documentation as reference.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
1. **PDM does NOT have app auto-updater** - it's a server-side app with manual updates
|
||||
2. **PDM uses Yew (Rust WASM)** - completely different from tftsr's React/TypeScript
|
||||
3. **PDM is AGPL-3** - cannot copy code, must implement from scratch
|
||||
4. **tftsr has solid foundation** - many UI components exist, need backend integration
|
||||
5. **Documentation overstated** - "100% complete" claims need correction
|
||||
6. **Auto-updater for tftsr** - Will use Tauri's updater plugin with Gitea releases API
|
||||
7. **Version bump required** - Cargo.toml (1.2.0 → 1.2.1), tauri.conf.json (1.1.0 → 1.2.1)
|
||||
8. **Updater location** - No updater currently exists in Proxmox section; this issue is about adding app auto-updater to Settings, not moving existing Proxmox updates
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** 2026-06-12
|
||||
**Author:** AI Assistant
|
||||
**Status:** Planning Phase - Ready for User Approval
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "trcaa"
|
||||
version = "1.2.1"
|
||||
version = "1.2.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@ -17,7 +17,6 @@ tauri-plugin-dialog = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-http = "2"
|
||||
tauri-plugin-updater = "2"
|
||||
rusqlite = { version = "0.31", features = ["bundled-sqlcipher-vendored-openssl"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
@ -5,7 +5,6 @@ use crate::ollama::{
|
||||
};
|
||||
use crate::state::{AppSettings, AppState, ProviderConfig};
|
||||
use std::env;
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
|
||||
// --- Ollama commands ---
|
||||
|
||||
@ -464,41 +463,3 @@ mod sudo_tests {
|
||||
assert_eq!(result, env_user);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Updater commands ---
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_app_updates(app: tauri::AppHandle) -> Result<bool, String> {
|
||||
match app.updater() {
|
||||
Ok(updater) => match updater.check().await {
|
||||
Ok(update) => Ok(update.is_some()),
|
||||
Err(e) => Err(format!("Failed to check for updates: {e}")),
|
||||
},
|
||||
Err(e) => Err(format!("Failed to get updater: {e}")),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_app_updates(app: tauri::AppHandle) -> Result<(), String> {
|
||||
match app.updater() {
|
||||
Ok(updater) => match updater.check().await {
|
||||
Ok(Some(update)) => match update.download_and_install(|_, _| {}, || {}).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(format!("Failed to install update: {e}")),
|
||||
},
|
||||
Ok(None) => Err("No update available".to_string()),
|
||||
Err(e) => Err(format!("Failed to check for updates: {e}")),
|
||||
},
|
||||
Err(e) => Err(format!("Failed to get updater: {e}")),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_update_channel() -> Result<String, String> {
|
||||
Ok("stable".to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_update_channel(_channel: String) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -71,7 +71,6 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.manage(app_state)
|
||||
.setup(|app| {
|
||||
let handle = app.handle().clone();
|
||||
@ -225,10 +224,6 @@ pub fn run() {
|
||||
commands::system::get_sudo_config_status,
|
||||
commands::system::test_sudo_password,
|
||||
commands::system::clear_sudo_password,
|
||||
commands::system::check_app_updates,
|
||||
commands::system::install_app_updates,
|
||||
commands::system::get_update_channel,
|
||||
commands::system::set_update_channel,
|
||||
// MCP Servers
|
||||
mcp::commands::list_mcp_servers,
|
||||
mcp::commands::create_mcp_server,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"productName": "Troubleshooting and RCA Assistant",
|
||||
"version": "1.2.1",
|
||||
"version": "1.1.0",
|
||||
"identifier": "com.trcaa.app",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
|
||||
@ -36,7 +36,6 @@ import MCPServers from "@/pages/Settings/MCPServers";
|
||||
import Security from "@/pages/Settings/Security";
|
||||
import ShellExecution from "@/pages/Settings/ShellExecution";
|
||||
import KubeconfigManager from "@/pages/Settings/KubeconfigManager";
|
||||
import { Updater } from "@/pages/Settings/Updater";
|
||||
import { KubernetesPage } from "@/pages/Kubernetes/KubernetesPage";
|
||||
import { ShellApprovalModal } from "@/components/ShellApprovalModal";
|
||||
import { ProxmoxRemotesPage } from "@/pages/Proxmox/RemotesPage";
|
||||
@ -69,11 +68,10 @@ const settingsItems = [
|
||||
{ to: "/settings/integrations", icon: Link, label: "Integrations" },
|
||||
{ to: "/settings/mcp", icon: Plug, label: "MCP Servers" },
|
||||
{ to: "/settings/security", icon: Shield, label: "Security" },
|
||||
{ to: "/settings/updater", icon: Clock, label: "Updater" },
|
||||
];
|
||||
|
||||
export default function App() {
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [appVersion, setAppVersion] = useState("");
|
||||
const { theme, setTheme, setProviders, getActiveProvider } = useSettingsStore();
|
||||
const cleanupDone = useRef(false);
|
||||
@ -238,7 +236,6 @@ export default function App() {
|
||||
<Route path="/proxmox/ha" element={<ProxmoxHAPage />} />
|
||||
<Route path="/proxmox/tasks" element={<ProxmoxTasksPage />} />
|
||||
<Route path="/proxmox/certificates" element={<ProxmoxCertificatesPage />} />
|
||||
<Route path="/settings/updater" element={<Updater />} />
|
||||
<Route path="/settings/integrations" element={<Integrations />} />
|
||||
<Route path="/settings/mcp" element={<MCPServers />} />
|
||||
<Route path="/settings/security" element={<Security />} />
|
||||
|
||||
@ -639,20 +639,6 @@ export const clearSudoPasswordCmd = () =>
|
||||
export const getAppVersionCmd = () =>
|
||||
invoke<string>("get_app_version");
|
||||
|
||||
// ─── Updater ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export const checkAppUpdatesCmd = async (): Promise<boolean> =>
|
||||
invoke<boolean>("check_app_updates");
|
||||
|
||||
export const installAppUpdatesCmd = async (): Promise<void> =>
|
||||
invoke<void>("install_app_updates");
|
||||
|
||||
export const getUpdateChannelCmd = async (): Promise<string> =>
|
||||
invoke<string>("get_update_channel");
|
||||
|
||||
export const setUpdateChannelCmd = async (channel: string): Promise<void> =>
|
||||
invoke<void>("set_update_channel", { channel });
|
||||
|
||||
// ─── Attachment cross-incident types ─────────────────────────────────────────
|
||||
|
||||
export interface LogFileSummary {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { RemotesList } from '@/components/Proxmox';
|
||||
@ -6,8 +6,6 @@ import { AddRemoteForm } from '@/components/Proxmox';
|
||||
import { EditRemoteForm } from '@/components/Proxmox';
|
||||
import { RemoveRemoteDialog } from '@/components/Proxmox';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/index';
|
||||
import { listProxmoxClusters, addProxmoxCluster, removeProxmoxCluster } from '@/lib/proxmoxClient';
|
||||
import { ClusterType } from '@/lib/domain';
|
||||
|
||||
interface RemoteInfo {
|
||||
id: string;
|
||||
@ -19,92 +17,38 @@ interface RemoteInfo {
|
||||
}
|
||||
|
||||
export function ProxmoxRemotesPage() {
|
||||
const [remotes, setRemotes] = useState<RemoteInfo[]>([]);
|
||||
const [remotes, setRemotes] = useState<RemoteInfo[]>([
|
||||
{ id: '1', name: 'Production Cluster', url: 'https://pve1.example.com:8006', username: 'root@pam', type: 'pve', status: 'connected' },
|
||||
{ id: '2', name: 'Backup Server', url: 'https://pbs1.example.com:8007', username: 'root@pam', type: 'pbs', status: 'connected' },
|
||||
]);
|
||||
const [showAddDialog, setShowAddDialog] = useState(false);
|
||||
const [editingRemote, setEditingRemote] = useState<RemoteInfo | null>(null);
|
||||
const [removingRemote, setRemovingRemote] = useState<RemoteInfo | null>(null);
|
||||
|
||||
const loadRemotes = async () => {
|
||||
try {
|
||||
const clusters = await listProxmoxClusters();
|
||||
const remotesList: RemoteInfo[] = clusters.map((c) => ({
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
url: c.url,
|
||||
username: c.username,
|
||||
type: c.clusterType === 've' ? 'pve' : 'pbs',
|
||||
status: 'connected' as const,
|
||||
}));
|
||||
setRemotes(remotesList);
|
||||
} catch (err) {
|
||||
console.error('Failed to load remotes:', err);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
void loadRemotes();
|
||||
}, []);
|
||||
|
||||
const generateId = (): string => {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleAddRemote = (config: any) => {
|
||||
const newRemote: RemoteInfo = {
|
||||
id: String(remotes.length + 1),
|
||||
name: String(config.name),
|
||||
url: String(config.url),
|
||||
username: String(config.username),
|
||||
type: config.type as 'pve' | 'pbs',
|
||||
status: 'connected',
|
||||
};
|
||||
setRemotes([...remotes, newRemote]);
|
||||
setShowAddDialog(false);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleAddRemote = async (config: any) => {
|
||||
try {
|
||||
const clusterType = config.type === 'pve' ? 've' : 'pbs';
|
||||
const url = config.url.replace(/^https?:\/\//, '');
|
||||
const port = config.type === 'pve' ? 8006 : 8007;
|
||||
const id = config.id || generateId();
|
||||
await addProxmoxCluster(
|
||||
id,
|
||||
config.name,
|
||||
clusterType as ClusterType,
|
||||
{ url, port },
|
||||
config.username,
|
||||
config.password || ''
|
||||
);
|
||||
await loadRemotes();
|
||||
setShowAddDialog(false);
|
||||
} catch (err) {
|
||||
console.error('Failed to add remote:', err);
|
||||
alert('Failed to add remote: ' + String(err));
|
||||
}
|
||||
const handleEditRemote = (config: any) => {
|
||||
setRemotes(remotes.map(r => r.id === String(config.id) ? { ...r, ...config } as RemoteInfo : r));
|
||||
setEditingRemote(null);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleEditRemote = async (config: any) => {
|
||||
try {
|
||||
const clusterType = config.type === 'pve' ? 've' : 'pbs';
|
||||
const url = config.url.replace(/^https?:\/\//, '');
|
||||
const port = config.type === 'pve' ? 8006 : 8007;
|
||||
await removeProxmoxCluster(config.id);
|
||||
await addProxmoxCluster(
|
||||
config.id,
|
||||
config.name,
|
||||
clusterType as ClusterType,
|
||||
{ url, port },
|
||||
config.username,
|
||||
config.password || ''
|
||||
);
|
||||
await loadRemotes();
|
||||
setEditingRemote(null);
|
||||
} catch (err) {
|
||||
console.error('Failed to edit remote:', err);
|
||||
alert('Failed to edit remote: ' + String(err));
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveRemote = async () => {
|
||||
const handleRemoveRemote = () => {
|
||||
if (removingRemote) {
|
||||
try {
|
||||
await removeProxmoxCluster(removingRemote.id);
|
||||
await loadRemotes();
|
||||
setRemovingRemote(null);
|
||||
} catch (err) {
|
||||
console.error('Failed to remove remote:', err);
|
||||
alert('Failed to remove remote: ' + String(err));
|
||||
}
|
||||
setRemotes(remotes.filter(r => r.id !== removingRemote.id));
|
||||
setRemovingRemote(null);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,170 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { RefreshCw, Check, AlertCircle, Loader } from 'lucide-react';
|
||||
import {
|
||||
checkAppUpdatesCmd,
|
||||
installAppUpdatesCmd,
|
||||
getUpdateChannelCmd,
|
||||
setUpdateChannelCmd,
|
||||
} from '@/lib/tauriCommands';
|
||||
|
||||
export function Updater() {
|
||||
const [channel, setChannel] = useState('stable');
|
||||
const [checking, setChecking] = useState(false);
|
||||
const [updateAvailable, setUpdateAvailable] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const loadChannel = async () => {
|
||||
try {
|
||||
const ch = await getUpdateChannelCmd();
|
||||
setChannel(ch);
|
||||
} catch {
|
||||
console.error('Failed to load channel');
|
||||
}
|
||||
};
|
||||
|
||||
const checkForUpdates = async () => {
|
||||
setChecking(true);
|
||||
setError(null);
|
||||
try {
|
||||
const available = await checkAppUpdatesCmd();
|
||||
setUpdateAvailable(available);
|
||||
} catch {
|
||||
setError('Failed to check for updates');
|
||||
} finally {
|
||||
setChecking(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInstallUpdate = async () => {
|
||||
try {
|
||||
await installAppUpdatesCmd();
|
||||
setUpdateAvailable(false);
|
||||
} catch {
|
||||
setError('Failed to install update');
|
||||
}
|
||||
};
|
||||
|
||||
const handleChannelChange = async (newChannel: string) => {
|
||||
setChannel(newChannel);
|
||||
try {
|
||||
await setUpdateChannelCmd(newChannel);
|
||||
} catch {
|
||||
setError('Failed to update channel');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
void loadChannel();
|
||||
void checkForUpdates();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Updater</h1>
|
||||
<p className="text-muted-foreground">Configure application auto-updates</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Update Channel</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex space-x-4">
|
||||
<button
|
||||
onClick={() => handleChannelChange('stable')}
|
||||
className={`flex-1 p-4 rounded-lg border-2 transition-all ${
|
||||
channel === 'stable'
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-border hover:border-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
<div className="font-semibold">Stable</div>
|
||||
<div className="text-sm text-muted-foreground">Production-ready releases</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleChannelChange('pre-release')}
|
||||
className={`flex-1 p-4 rounded-lg border-2 transition-all ${
|
||||
channel === 'pre-release'
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-border hover:border-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
<div className="font-semibold">Pre-Release</div>
|
||||
<div className="text-sm text-muted-foreground">Latest development builds</div>
|
||||
</button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle>Check for Updates</CardTitle>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={checkForUpdates}
|
||||
disabled={checking}
|
||||
>
|
||||
{checking ? (
|
||||
<>
|
||||
<Loader className="mr-2 h-4 w-4 animate-spin" />
|
||||
Checking...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Check Now
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{error && (
|
||||
<div className="mb-4 flex items-center space-x-2 rounded-lg bg-destructive/15 p-3 text-destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<span className="text-sm">{error}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{updateAvailable ? (
|
||||
<div className="flex items-center justify-between rounded-lg bg-green-50 p-4 dark:bg-green-900/20">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="rounded-full bg-green-600 p-1 text-white">
|
||||
<Check className="h-4 w-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold text-green-900 dark:text-green-100">
|
||||
Update Available
|
||||
</div>
|
||||
<div className="text-sm text-green-700 dark:text-green-300">
|
||||
A new version is ready to install
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={handleInstallUpdate}>
|
||||
Install Update
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-between rounded-lg bg-muted p-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="rounded-full bg-muted-foreground p-1 text-background">
|
||||
<Check className="h-4 w-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold">Up to Date</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
You are running the latest version
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user