fix(proxmox): address PR review findings
Some checks failed
Test / frontend-tests (pull_request) Successful in 1m38s
Test / frontend-typecheck (pull_request) Successful in 1m46s
PR Review Automation / review (pull_request) Successful in 5m37s
Test / rust-fmt-check (pull_request) Failing after 12m26s
Test / rust-clippy (pull_request) Successful in 13m43s
Test / rust-tests (pull_request) Successful in 16m4s
Some checks failed
Test / frontend-tests (pull_request) Successful in 1m38s
Test / frontend-typecheck (pull_request) Successful in 1m46s
PR Review Automation / review (pull_request) Successful in 5m37s
Test / rust-fmt-check (pull_request) Failing after 12m26s
Test / rust-clippy (pull_request) Successful in 13m43s
Test / rust-tests (pull_request) Successful in 16m4s
- Storage: prevent double-slash ID when cluster/resources returns
shared storage without a node field (e.g. "storage//PBS_TFTSR"
→ "storage/PBS_TFTSR")
- Firewall: add comment explaining why rule_num is omitted from
add_rule — PVE assigns position (pos) automatically on creation
- Network: replace misleading "implementation pending" toasts with
an immediate warning; dialog no longer opens since backend commands
(POST/PUT/DELETE nodes/{node}/network) are not yet implemented
- Backup: same treatment for New Job — warns immediately instead of
opening a form that silently does nothing
- VMList: add comment explaining handleVMAction receives clusterId
from props (not a stale closure over state); add inline comment
clarifying the migration button disabled conditions
This commit is contained in:
parent
671ee51626
commit
58b4d59e6d
@ -709,13 +709,13 @@ pub async fn list_proxmox_datastores(
|
||||
let storage_name = obj.get("storage").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let node_name = obj.get("node").and_then(|v| v.as_str()).unwrap_or("");
|
||||
|
||||
normalized.insert(
|
||||
"id".to_string(),
|
||||
serde_json::Value::String(format!(
|
||||
"storage/{}/{}",
|
||||
node_name, storage_name
|
||||
)),
|
||||
);
|
||||
// Avoid double-slash when cluster/resources omits "node" for shared storage
|
||||
let storage_id = if node_name.is_empty() {
|
||||
format!("storage/{}", storage_name)
|
||||
} else {
|
||||
format!("storage/{}/{}", node_name, storage_name)
|
||||
};
|
||||
normalized.insert("id".to_string(), serde_json::Value::String(storage_id));
|
||||
normalized.insert(
|
||||
"storage".to_string(),
|
||||
serde_json::Value::String(storage_name.to_string()),
|
||||
|
||||
@ -80,6 +80,8 @@ pub async fn list_firewall_rules(
|
||||
}
|
||||
|
||||
/// Add firewall rule — uses correct PVE API field names (proto, enable, dest).
|
||||
/// `rule.rule_num` is intentionally not sent: PVE assigns the position (pos) automatically
|
||||
/// on creation. rule_num is only used for update/delete operations on existing rules.
|
||||
pub async fn add_rule(
|
||||
client: &crate::proxmox::client::ProxmoxClient,
|
||||
node: &str,
|
||||
|
||||
@ -122,6 +122,8 @@ export function VMList({
|
||||
}));
|
||||
}, [rawVms]);
|
||||
|
||||
// clusterId comes from props (not captured via closure over state), so it is always
|
||||
// current when an action fires even if the user switches clusters mid-session.
|
||||
const handleVMAction = useCallback(async (vm: VMInfo, action: string) => {
|
||||
if (!clusterId) {
|
||||
toast.error('No cluster selected');
|
||||
@ -746,6 +748,8 @@ function MigrationDialog({
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
{/* Disabled when: no target node typed/selected,
|
||||
OR same-cluster migration with no enumerated nodes to choose from */}
|
||||
<Button
|
||||
onClick={onSubmit}
|
||||
disabled={!targetNode || (!isCrossCluster && availableTargets.length === 0)}
|
||||
|
||||
@ -80,26 +80,14 @@ export function ProxmoxBackupPage() {
|
||||
}, [selectedClusterId, loadJobs]);
|
||||
|
||||
const handleNewJob = () => {
|
||||
setJobName('');
|
||||
setJobNode('');
|
||||
setJobSchedule('');
|
||||
setJobVms('');
|
||||
setShowNewJobDialog(true);
|
||||
toast.warning(
|
||||
'Backup job creation requires additional backend implementation (POST cluster/backup) and is not yet available.',
|
||||
);
|
||||
};
|
||||
|
||||
const handleSubmitNewJob = async () => {
|
||||
if (!jobName || !jobNode || !jobSchedule) {
|
||||
toast.error('Job name, node, and schedule are required');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
toast.info(`Creating backup job ${jobName} - implementation pending`);
|
||||
setShowNewJobDialog(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to create backup job:', error);
|
||||
toast.error(`Failed to create backup job: ${error}`);
|
||||
}
|
||||
toast.warning('Backup job creation is not yet available.');
|
||||
setShowNewJobDialog(false);
|
||||
};
|
||||
|
||||
if (clusters.length === 0 && !isLoading) {
|
||||
|
||||
@ -17,7 +17,7 @@ export function ProxmoxNetworkPage() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [showAddDialog, setShowAddDialog] = useState(false);
|
||||
const [editingInterface, setEditingInterface] = useState<NetworkInterface | null>(null);
|
||||
const [editingInterface] = useState<NetworkInterface | null>(null);
|
||||
|
||||
// Form state
|
||||
const [ifaceName, setIfaceName] = useState('');
|
||||
@ -52,58 +52,24 @@ export function ProxmoxNetworkPage() {
|
||||
.catch(console.error);
|
||||
}, [loadInterfaces, nodeId]);
|
||||
|
||||
const NOT_IMPLEMENTED_MSG =
|
||||
'Network interface management requires additional backend implementation (POST/PUT/DELETE nodes/{node}/network) and is not yet available.';
|
||||
|
||||
const handleAddInterface = () => {
|
||||
setEditingInterface(null);
|
||||
setIfaceName('');
|
||||
setIfaceType('eth');
|
||||
setAddress('');
|
||||
setNetmask('');
|
||||
setGateway('');
|
||||
setActive(true);
|
||||
setShowAddDialog(true);
|
||||
toast.warning(NOT_IMPLEMENTED_MSG);
|
||||
};
|
||||
|
||||
const handleEditInterface = (iface: NetworkInterface) => {
|
||||
setEditingInterface(iface);
|
||||
setIfaceName(iface.iface);
|
||||
setIfaceType(iface.type);
|
||||
setAddress(iface.address || '');
|
||||
setNetmask(iface.netmask || '');
|
||||
setGateway(iface.gateway || '');
|
||||
setActive(iface.active);
|
||||
setShowAddDialog(true);
|
||||
const handleEditInterface = (_iface: NetworkInterface) => {
|
||||
toast.warning(NOT_IMPLEMENTED_MSG);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!ifaceName || !ifaceType) {
|
||||
toast.error('Interface name and type are required');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (editingInterface) {
|
||||
toast.info(`Updating interface ${ifaceName} - implementation pending`);
|
||||
} else {
|
||||
toast.info(`Creating interface ${ifaceName} - implementation pending`);
|
||||
}
|
||||
setShowAddDialog(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to save interface:', error);
|
||||
toast.error(`Failed to save interface: ${error}`);
|
||||
}
|
||||
toast.warning(NOT_IMPLEMENTED_MSG);
|
||||
setShowAddDialog(false);
|
||||
};
|
||||
|
||||
const handleDeleteInterface = async (iface: NetworkInterface) => {
|
||||
if (!confirm(`Are you sure you want to delete interface ${iface.iface}?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
toast.info(`Deleting interface ${iface.iface} - implementation pending`);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete interface:', error);
|
||||
toast.error(`Failed to delete interface: ${error}`);
|
||||
}
|
||||
const handleDeleteInterface = async (_iface: NetworkInterface) => {
|
||||
toast.warning(NOT_IMPLEMENTED_MSG);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user