fix(addressing): accept device UUID as device_slug in SdPath

The frontend VolumesGroup was sending volume.device_id (UUID) instead
of device.slug when constructing Physical SdPaths. This caused
as_local_path() and is_local() to return false, breaking ephemeral
volume browsing ("Location root path is not local").

Backend: SdPath::is_current_device() now accepts slug, UUID, or "local".
Frontend: VolumesGroup.tsx looks up the Device by ID and uses device.slug.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
slvnlrt
2026-03-14 22:36:39 +01:00
parent bd428bf131
commit 27eefd1c67
2 changed files with 28 additions and 9 deletions

View File

@@ -199,13 +199,21 @@ impl SdPath {
}
}
/// Check if the given device_slug refers to this device.
/// Accepts the slug, the UUID string, or the "local" placeholder.
fn is_current_device(device_slug: &str) -> bool {
device_slug == "local"
|| device_slug == get_current_device_slug()
|| device_slug == get_current_device_id().to_string()
}
/// Check if this path is on the current device
pub fn is_local(&self) -> bool {
match self {
Self::Physical { device_slug, .. } => *device_slug == get_current_device_slug(),
Self::Cloud { .. } => false, // Cloud paths are never local
Self::Content { .. } => false, // Content paths are abstract, not inherently local
Self::Sidecar { .. } => false, // Sidecar paths are abstract, must be resolved
Self::Physical { device_slug, .. } => Self::is_current_device(device_slug),
Self::Cloud { .. } => false,
Self::Content { .. } => false,
Self::Sidecar { .. } => false,
}
}
@@ -213,8 +221,7 @@ impl SdPath {
pub fn as_local_path(&self) -> Option<&Path> {
match self {
Self::Physical { device_slug, path } => {
// "local" is a special placeholder from the frontend meaning "current device"
if *device_slug == "local" || *device_slug == get_current_device_slug() {
if Self::is_current_device(device_slug) {
Some(path)
} else {
None

View File

@@ -1,6 +1,6 @@
import {EyeSlash, Plugs, WifiSlash} from '@phosphor-icons/react';
import {getVolumeIcon, useNormalizedQuery} from '@sd/ts-client';
import type {Volume} from '@sd/ts-client';
import type {Device, Volume} from '@sd/ts-client';
import {useNavigate} from 'react-router-dom';
import {GroupHeader} from './GroupHeader';
import {SpaceItem} from './SpaceItem';
@@ -29,9 +29,13 @@ const getVolumeIndicator = (volume: Volume) => (
);
// Component for individual volume items with context menu
function VolumeItem({volume, index, volumesLength}: {volume: Volume; index: number; volumesLength: number}) {
function VolumeItem({volume, index, volumesLength, devices}: {volume: Volume; index: number; volumesLength: number; devices: Device[]}) {
const contextMenu = useVolumeContextMenu({volume});
// Look up the device by ID to get the slug (not the UUID)
const device = devices.find((d) => d.id === volume.device_id);
const deviceSlug = device?.slug || 'local';
return (
<SpaceItem
key={volume.id}
@@ -47,7 +51,7 @@ function VolumeItem({volume, index, volumesLength}: {volume: Volume; index: numb
} as any
}
volumeData={{
device_slug: volume.device_id,
device_slug: deviceSlug,
mount_path: volume.mount_point || '/'
}}
rightComponent={getVolumeIndicator(volume)}
@@ -72,7 +76,14 @@ export function VolumesGroup({
resourceType: 'volume'
});
const {data: devicesData} = useNormalizedQuery<any, Device[]>({
query: 'devices.list',
input: {include_offline: true, include_details: false},
resourceType: 'device'
});
const volumes = volumesData?.volumes || [];
const devices = (devicesData as Device[]) || [];
return (
<div>
@@ -98,6 +109,7 @@ export function VolumesGroup({
volume={volume}
index={index}
volumesLength={volumes.length}
devices={devices}
/>
))
)}