feat: add system devices toggle in device selection modal

Add a toggle button to show/hide system devices in the device selection modal. This prevents users from accidentally selecting system drives while still allowing advanced users to access them when needed.

Changes:
- Add showSystemDevices state toggle in DeviceModal
- Filter devices based on toggle state (default: hide system devices)
- Redesign warning banner with toggle badge button
- Add Shield icon and translations for all 15 languages
- Update CSS with new banner layout and toggle button styles
This commit is contained in:
SuperKali
2025-12-26 10:26:11 +01:00
parent 07879be87c
commit d81df9e463
17 changed files with 194 additions and 23 deletions

View File

@@ -75,6 +75,7 @@ export function DeviceModal({ isOpen, onClose, onSelect }: DeviceModalProps) {
const [selectedDevice, setSelectedDevice] = useState<BlockDevice | null>(null);
const [showConfirm, setShowConfirm] = useState(false);
const [showSkeleton, setShowSkeleton] = useState(false);
const [showSystemDevices, setShowSystemDevices] = useState(false);
// Track previous devices for change detection
const prevDevicesRef = useRef<BlockDevice[] | null>(null);
@@ -92,6 +93,14 @@ export function DeviceModal({ isOpen, onClose, onSelect }: DeviceModalProps) {
return devices && devices.length > 0;
}, [devices]);
// Filter devices based on showSystemDevices toggle
const filteredDevices = useMemo(() => {
if (showSystemDevices) {
return devices;
}
return devices.filter(d => !d.is_system);
}, [devices, showSystemDevices]);
// Show skeleton with minimum delay
useEffect(() => {
let skeletonTimeout: NodeJS.Timeout;
@@ -158,10 +167,28 @@ export function DeviceModal({ isOpen, onClose, onSelect }: DeviceModalProps) {
return (
<>
<Modal isOpen={isOpen && !showConfirm} onClose={onClose} title={t('modal.selectDevice')}>
<div className="modal-warning-banner">
<AlertTriangle size={16} />
<span>{t('flash.dataWarning')}</span>
<Modal
isOpen={isOpen && !showConfirm}
onClose={onClose}
title={t('modal.selectDevice')}
>
<div className="device-warning-banner">
<div className="device-warning-banner-content">
<div className="device-warning-banner-icon">
<AlertTriangle size={16} />
</div>
<div className="device-warning-banner-title">
{t('flash.dataWarning')}
</div>
</div>
<button
onClick={() => setShowSystemDevices(!showSystemDevices)}
className={`system-devices-badge ${showSystemDevices ? 'active' : ''}`}
>
<Shield size={13} />
<span>{showSystemDevices ? t('device.hideSystemDevices') : t('device.showSystemDevices')}</span>
</button>
</div>
{error ? (
@@ -169,7 +196,7 @@ export function DeviceModal({ isOpen, onClose, onSelect }: DeviceModalProps) {
) : (
<>
{showSkeleton && <ListItemSkeleton count={4} />}
{devices.length === 0 && !showSkeleton && (
{filteredDevices.length === 0 && !showSkeleton && (
<div className="no-results">
<Usb size={40} />
<p>{t('modal.noDevices')}</p>
@@ -183,7 +210,7 @@ export function DeviceModal({ isOpen, onClose, onSelect }: DeviceModalProps) {
</div>
)}
<div className="modal-list no-animations">
{!showSkeleton && devices.map((device) => {
{!showSkeleton && filteredDevices.map((device) => {
const deviceType = getDeviceType(device);
const badge = getDeviceBadge(deviceType, t);
return (
@@ -217,7 +244,7 @@ export function DeviceModal({ isOpen, onClose, onSelect }: DeviceModalProps) {
);
})}
</div>
{!showSkeleton && devices.length > 0 && (
{!showSkeleton && filteredDevices.length > 0 && (
<div className="modal-refresh-bottom">
<button className="btn btn-secondary" onClick={reload} disabled={loading}>
<RefreshCw size={14} className={loading ? 'spin' : ''} />

View File

@@ -63,7 +63,9 @@
"sata": "SATA",
"sas": "SAS",
"nvme": "NVMe",
"refresh": "Aktualisieren"
"refresh": "Aktualisieren",
"showSystemDevices": "Systemlaufwerke anzeigen",
"hideSystemDevices": "Systemlaufwerke ausblenden"
},
"header": {
"stepManufacturer": "Hersteller",

View File

@@ -63,7 +63,9 @@
"sata": "SATA",
"sas": "SAS",
"nvme": "NVMe",
"refresh": "Refresh"
"refresh": "Refresh",
"showSystemDevices": "Show system drives",
"hideSystemDevices": "Hide system drives"
},
"header": {
"stepManufacturer": "Manufacturer",

View File

@@ -63,7 +63,9 @@
"sata": "SATA",
"sas": "SAS",
"nvme": "NVMe",
"refresh": "Actualizar"
"refresh": "Actualizar",
"showSystemDevices": "Mostrar unidades del sistema",
"hideSystemDevices": "Ocultar unidades del sistema"
},
"header": {
"stepManufacturer": "Fabricante",

View File

@@ -63,7 +63,9 @@
"sata": "SATA",
"sas": "SAS",
"nvme": "NVMe",
"refresh": "Actualiser"
"refresh": "Actualiser",
"showSystemDevices": "Afficher les disques système",
"hideSystemDevices": "Masquer les disques système"
},
"header": {
"stepManufacturer": "Fabricant",

View File

@@ -63,7 +63,9 @@
"sata": "SATA",
"sas": "SAS",
"nvme": "NVMe",
"refresh": "Aggiorna"
"refresh": "Aggiorna",
"showSystemDevices": "Mostra dischi di sistema",
"hideSystemDevices": "Nascondi dischi di sistema"
},
"header": {
"stepManufacturer": "Produttore",

View File

@@ -63,7 +63,9 @@
"sata": "SATA",
"sas": "SAS",
"nvme": "NVMe",
"refresh": "更新"
"refresh": "更新",
"showSystemDevices": "システムドライブを表示",
"hideSystemDevices": "システムドライブを非表示"
},
"header": {
"stepManufacturer": "メーカー",

View File

@@ -63,7 +63,9 @@
"sata": "SATA",
"sas": "SAS",
"nvme": "NVMe",
"refresh": "새로고침"
"refresh": "새로고침",
"showSystemDevices": "시스템 드라이브 표시",
"hideSystemDevices": "시스템 드라이브 숨기기"
},
"header": {
"stepManufacturer": "제조사",

View File

@@ -63,7 +63,9 @@
"sata": "SATA",
"sas": "SAS",
"nvme": "NVMe",
"refresh": "Vernieuwen"
"refresh": "Vernieuwen",
"showSystemDevices": "Systeemstations weergeven",
"hideSystemDevices": "Systeemstations verbergen"
},
"header": {
"stepManufacturer": "Fabrikant",

View File

@@ -63,7 +63,9 @@
"sata": "SATA",
"sas": "SAS",
"nvme": "NVMe",
"refresh": "Odśwież"
"refresh": "Odśwież",
"showSystemDevices": "Pokaż dyski systemowe",
"hideSystemDevices": "Ukryj dyski systemowe"
},
"header": {
"stepManufacturer": "Producent",

View File

@@ -63,7 +63,9 @@
"sata": "SATA",
"sas": "SAS",
"nvme": "NVMe",
"refresh": "Atualizar"
"refresh": "Atualizar",
"showSystemDevices": "Mostrar unidades do sistema",
"hideSystemDevices": "Ocultar unidades do sistema"
},
"header": {
"stepManufacturer": "Fabricante",

View File

@@ -63,7 +63,9 @@
"sata": "SATA",
"sas": "SAS",
"nvme": "NVMe",
"refresh": "Обновить"
"refresh": "Обновить",
"showSystemDevices": "Показать системные диски",
"hideSystemDevices": "Скрыть системные диски"
},
"header": {
"stepManufacturer": "Производитель",

View File

@@ -63,7 +63,9 @@
"sata": "SATA",
"sas": "SAS",
"nvme": "NVMe",
"refresh": "Osveži"
"refresh": "Osveži",
"showSystemDevices": "Prikaži sistemske pogone",
"hideSystemDevices": "Skrij sistemske pogone"
},
"header": {
"stepManufacturer": "Proizvajalec",

View File

@@ -63,7 +63,9 @@
"sata": "SATA",
"sas": "SAS",
"nvme": "NVMe",
"refresh": "Yenile"
"refresh": "Yenile",
"showSystemDevices": "Sistem sürücülerini göster",
"hideSystemDevices": "Sistem sürücülerini gizle"
},
"header": {
"stepManufacturer": "Üretici",

View File

@@ -63,7 +63,9 @@
"sata": "SATA",
"sas": "SAS",
"nvme": "NVMe",
"refresh": "Оновити"
"refresh": "Оновити",
"showSystemDevices": "Показати системні диски",
"hideSystemDevices": "Приховати системні диски"
},
"header": {
"stepManufacturer": "Виробник",

View File

@@ -63,7 +63,9 @@
"sata": "SATA",
"sas": "SAS",
"nvme": "NVMe",
"refresh": "刷新"
"refresh": "刷新",
"showSystemDevices": "显示系统驱动器",
"hideSystemDevices": "隐藏系统驱动器"
},
"header": {
"stepManufacturer": "制造商",

View File

@@ -695,7 +695,121 @@
}
/* ========================================
DEVICE SECTION
DEVICE WARNING BANNER
======================================== */
.device-warning-banner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 12px 16px;
background: rgba(239, 68, 68, 0.08);
border-bottom: 1px solid rgba(239, 68, 68, 0.15);
}
@media (prefers-color-scheme: dark) {
.device-warning-banner {
background: rgba(239, 68, 68, 0.12);
border-bottom-color: rgba(239, 68, 68, 0.2);
}
}
.device-warning-banner-content {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.device-warning-banner-icon {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 8px;
background: rgba(239, 68, 68, 0.15);
flex-shrink: 0;
}
@media (prefers-color-scheme: dark) {
.device-warning-banner-icon {
background: rgba(239, 68, 68, 0.2);
}
}
.device-warning-banner-icon svg {
color: var(--armbian-red);
}
.device-warning-banner-title {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
/* System Devices Badge */
.system-devices-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
border: 1px solid var(--border-color);
background-color: var(--bg-secondary);
color: var(--text-secondary);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
white-space: nowrap;
}
.system-devices-badge:hover {
background-color: var(--bg-hover);
border-color: var(--text-muted);
}
.system-devices-badge.active {
background-color: rgba(239, 68, 68, 0.12);
border-color: rgba(239, 68, 68, 0.3);
color: var(--armbian-red);
animation: badge-pulse 2s infinite;
}
.system-devices-badge.active:hover {
background-color: rgba(239, 68, 68, 0.18);
}
@media (prefers-color-scheme: dark) {
.system-devices-badge.active {
background-color: rgba(239, 68, 68, 0.18);
border-color: rgba(239, 68, 68, 0.4);
}
.system-devices-badge.active:hover {
background-color: rgba(239, 68, 68, 0.24);
}
}
@keyframes badge-pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.3); }
50% { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
}
/* Respect user motion preferences */
@media (prefers-reduced-motion: reduce) {
.system-devices-badge {
transition: background-color 0.2s ease, color 0.2s ease;
}
.system-devices-badge.active {
animation: none;
}
}
/* ========================================
DEVICE SECTION (Legacy)
======================================== */
.device-warning {
display: flex;