mirror of
https://github.com/luckfox-eng29/kvm.git
synced 2026-05-27 08:35:10 +02:00
feat(sd): add filesystem type selection for SD card format (exFAT/FAT32)
Signed-off-by: luckfox-eng29 <eng29@luckfox.com>
This commit is contained in:
@@ -67,6 +67,8 @@ interface StorageFilePageProps {
|
|||||||
onUnmountSDStorage?: () => void;
|
onUnmountSDStorage?: () => void;
|
||||||
onFormatSDStorage?: () => void;
|
onFormatSDStorage?: () => void;
|
||||||
onMountSDStorage?: () => void;
|
onMountSDStorage?: () => void;
|
||||||
|
fsType?: 'exfat' | 'fat32';
|
||||||
|
onFsTypeChange?: (value: 'exfat' | 'fat32') => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FileManager: React.FC<StorageFilePageProps> = ({
|
export const FileManager: React.FC<StorageFilePageProps> = ({
|
||||||
@@ -79,6 +81,8 @@ export const FileManager: React.FC<StorageFilePageProps> = ({
|
|||||||
onResetSDStorage,
|
onResetSDStorage,
|
||||||
onUnmountSDStorage,
|
onUnmountSDStorage,
|
||||||
onFormatSDStorage,
|
onFormatSDStorage,
|
||||||
|
fsType,
|
||||||
|
onFsTypeChange,
|
||||||
}) => {
|
}) => {
|
||||||
const { $at } = useReactAt();
|
const { $at } = useReactAt();
|
||||||
|
|
||||||
@@ -251,15 +255,28 @@ export const FileManager: React.FC<StorageFilePageProps> = ({
|
|||||||
</p>
|
</p>
|
||||||
{sdMountStatus !== "none" && (
|
{sdMountStatus !== "none" && (
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
<AntdButton
|
<div className="w-full space-y-2">
|
||||||
disabled={loading}
|
<p className="w-full text-left text-xs text-slate-700 dark:text-slate-300">
|
||||||
danger={true}
|
{$at("Choose the file system for MicroSD formatting")}
|
||||||
type="primary"
|
</p>
|
||||||
onClick={handleFormatWrapper}
|
<select
|
||||||
className="w-full text-red-500 dark:text-red-400 border-red-200 dark:border-red-800"
|
value={fsType || "fat32"}
|
||||||
>
|
onChange={(e) => onFsTypeChange?.(e.target.value as 'exfat' | 'fat32')}
|
||||||
{$at("Format MicroSD Card")}
|
style={{ width: "100%", padding: "8px", borderRadius: "4px" }}
|
||||||
</AntdButton>
|
>
|
||||||
|
<option value="fat32">FAT32</option>
|
||||||
|
<option value="exfat">exFAT</option>
|
||||||
|
</select>
|
||||||
|
<AntdButton
|
||||||
|
disabled={loading}
|
||||||
|
danger={true}
|
||||||
|
type="primary"
|
||||||
|
onClick={handleFormatWrapper}
|
||||||
|
className="w-full text-red-500 dark:text-red-400 border-red-200 dark:border-red-800"
|
||||||
|
>
|
||||||
|
{$at("Format MicroSD Card")} ({(fsType || "fat32")})
|
||||||
|
</AntdButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -318,6 +335,8 @@ export const FileManager: React.FC<StorageFilePageProps> = ({
|
|||||||
onUnmountSDStorage={handleUnmountWrapper}
|
onUnmountSDStorage={handleUnmountWrapper}
|
||||||
onFormatSDStorage={handleFormatWrapper}
|
onFormatSDStorage={handleFormatWrapper}
|
||||||
syncStorage={syncStorage}
|
syncStorage={syncStorage}
|
||||||
|
fsType={fsType}
|
||||||
|
onFsTypeChange={onFsTypeChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{uploadFile ? (
|
{uploadFile ? (
|
||||||
@@ -504,29 +523,46 @@ interface ActionButtonsSectionProps {
|
|||||||
onUnmountSDStorage?: () => void;
|
onUnmountSDStorage?: () => void;
|
||||||
onFormatSDStorage?: () => void;
|
onFormatSDStorage?: () => void;
|
||||||
syncStorage: () => void;
|
syncStorage: () => void;
|
||||||
|
fsType?: 'exfat' | 'fat32';
|
||||||
|
onFsTypeChange?: (value: 'exfat' | 'fat32') => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ActionButtonsSection: React.FC<ActionButtonsSectionProps> = ({
|
const ActionButtonsSection: React.FC<ActionButtonsSectionProps> = ({
|
||||||
mediaType,
|
mediaType,
|
||||||
loading,
|
loading,
|
||||||
showSDManagement,
|
showSDManagement,
|
||||||
onUnmountSDStorage,
|
onUnmountSDStorage,
|
||||||
onFormatSDStorage,
|
onFormatSDStorage,
|
||||||
}) => {
|
fsType,
|
||||||
|
onFsTypeChange,
|
||||||
|
}) => {
|
||||||
const { $at } = useReactAt();
|
const { $at } = useReactAt();
|
||||||
|
|
||||||
if (mediaType === "sd" && showSDManagement) {
|
if (mediaType === "sd" && showSDManagement) {
|
||||||
return (
|
return (
|
||||||
<div className="flex animate-fadeIn justify-between gap-2 opacity-0"
|
<div className="animate-fadeIn space-y-2 opacity-0"
|
||||||
style={{ animationDuration: "0.7s", animationDelay: "0.25s" }}
|
style={{ animationDuration: "0.7s", animationDelay: "0.25s" }}
|
||||||
>
|
>
|
||||||
<AntdButton
|
<div className="w-full space-y-2">
|
||||||
disabled={loading}
|
<p className="w-full text-left text-xs text-slate-700 dark:text-slate-300">
|
||||||
type="primary"
|
{$at("Choose the file system for MicroSD formatting")}
|
||||||
danger={true}
|
</p>
|
||||||
onClick={onFormatSDStorage}
|
<select
|
||||||
className="w-full text-red-500 dark:text-red-400 border-red-200 dark:border-red-800"
|
value={fsType || "fat32"}
|
||||||
>{$at("Format MicroSD Card")}</AntdButton>
|
onChange={(e) => onFsTypeChange?.(e.target.value as 'exfat' | 'fat32')}
|
||||||
|
style={{ width: "100%", padding: "8px", borderRadius: "4px" }}
|
||||||
|
>
|
||||||
|
<option value="fat32">FAT32</option>
|
||||||
|
<option value="exfat">exFAT</option>
|
||||||
|
</select>
|
||||||
|
<AntdButton
|
||||||
|
disabled={loading}
|
||||||
|
type="primary"
|
||||||
|
danger={true}
|
||||||
|
onClick={onFormatSDStorage}
|
||||||
|
className="w-full text-red-500 dark:text-red-400 border-red-200 dark:border-red-800"
|
||||||
|
>{$at("Format MicroSD Card")} ({(fsType || "fat32")})</AntdButton>
|
||||||
|
</div>
|
||||||
<AntdButton
|
<AntdButton
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export default function SDFilePage() {
|
|||||||
const { $at } = useReactAt();
|
const { $at } = useReactAt();
|
||||||
const [send] = useJsonRpc();
|
const [send] = useJsonRpc();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [fsType, setFsType] = useState<'exfat' | 'fat32'>('fat32');
|
||||||
|
|
||||||
const handleResetSDStorage = async () => {
|
const handleResetSDStorage = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -37,11 +38,11 @@ export default function SDFilePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleFormatSDStorage = async () => {
|
const handleFormatSDStorage = async () => {
|
||||||
if (!window.confirm($at("Formatting the SD card will erase all data. Continue?"))) {
|
if (!window.confirm($at(`Formatting the SD card as ${fsType.toUpperCase()} will erase all data. Continue?`))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
send("formatSDStorage", { confirm: true }, res => {
|
send("formatSDStorage", { confirm: true, fsType }, res => {
|
||||||
if ("error" in res) {
|
if ("error" in res) {
|
||||||
notifications.error(res.error.data || res.error.message);
|
notifications.error(res.error.data || res.error.message);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -54,17 +55,21 @@ export default function SDFilePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FileManager
|
<>
|
||||||
mediaType="sd"
|
<FileManager
|
||||||
returnTo="/sd-files"
|
mediaType="sd"
|
||||||
listFilesMethod="listSDStorageFiles"
|
returnTo="/sd-files"
|
||||||
getSpaceMethod="getSDStorageSpace"
|
listFilesMethod="listSDStorageFiles"
|
||||||
deleteFileMethod="deleteSDStorageFile"
|
getSpaceMethod="getSDStorageSpace"
|
||||||
downloadUrlPrefix="/storage/sd-download"
|
deleteFileMethod="deleteSDStorageFile"
|
||||||
showSDManagement={true}
|
downloadUrlPrefix="/storage/sd-download"
|
||||||
onResetSDStorage={handleResetSDStorage}
|
showSDManagement={true}
|
||||||
onUnmountSDStorage={handleUnmountSDStorage}
|
onResetSDStorage={handleResetSDStorage}
|
||||||
onFormatSDStorage={handleFormatSDStorage}
|
onUnmountSDStorage={handleUnmountSDStorage}
|
||||||
/>
|
onFormatSDStorage={handleFormatSDStorage}
|
||||||
|
fsType={fsType}
|
||||||
|
onFsTypeChange={setFsType}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ export default function ImageManager({
|
|||||||
const [sdMountStatus, setSDMountStatus] = useState<"ok" | "none" | "fail" | null>(storageType === 'sd' ? null : 'ok');
|
const [sdMountStatus, setSDMountStatus] = useState<"ok" | "none" | "fail" | null>(storageType === 'sd' ? null : 'ok');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [uploadFile, setUploadFile] = useState<string | null>(null);
|
const [uploadFile, setUploadFile] = useState<string | null>(null);
|
||||||
|
const [fsType, setFsType] = useState<'exfat' | 'fat32'>('fat32');
|
||||||
const filesPerPage = 5;
|
const filesPerPage = 5;
|
||||||
|
|
||||||
const percentageUsed = useMemo(() => {
|
const percentageUsed = useMemo(() => {
|
||||||
@@ -170,7 +171,7 @@ export default function ImageManager({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
send("formatSDStorage", { confirm: true }, res => {
|
send("formatSDStorage", { confirm: true, fsType }, res => {
|
||||||
if ("error" in res) {
|
if ("error" in res) {
|
||||||
notifications.error(res.error.data || res.error.message);
|
notifications.error(res.error.data || res.error.message);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -347,15 +348,28 @@ export default function ImageManager({
|
|||||||
</p>
|
</p>
|
||||||
{sdMountStatus !== "none" && (
|
{sdMountStatus !== "none" && (
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
<AntdButton
|
<div className="mx-auto w-full max-w-[360px] space-y-2">
|
||||||
disabled={loading}
|
<p className="w-full text-left text-xs text-slate-700 dark:text-slate-300">
|
||||||
danger={true}
|
{$at("Choose the file system for MicroSD formatting")}
|
||||||
type="primary"
|
</p>
|
||||||
onClick={handleFormatSDStorage}
|
<select
|
||||||
className="w-full text-red-500 dark:text-red-400 border-red-200 dark:border-red-800"
|
value={fsType}
|
||||||
>
|
onChange={(e) => setFsType(e.target.value as 'exfat' | 'fat32')}
|
||||||
{$at("Format MicroSD Card")}
|
style={{ width: "100%", padding: "8px", borderRadius: "4px" }}
|
||||||
</AntdButton>
|
>
|
||||||
|
<option value="fat32">FAT32</option>
|
||||||
|
<option value="exfat">exFAT</option>
|
||||||
|
</select>
|
||||||
|
<AntdButton
|
||||||
|
disabled={loading}
|
||||||
|
danger={true}
|
||||||
|
type="primary"
|
||||||
|
onClick={handleFormatSDStorage}
|
||||||
|
className="w-full text-red-500 dark:text-red-400 border-red-200 dark:border-red-800"
|
||||||
|
>
|
||||||
|
{$at("Format MicroSD Card")} ({fsType})
|
||||||
|
</AntdButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -493,16 +507,29 @@ export default function ImageManager({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{unmountApi && storageType === 'sd' && (
|
{unmountApi && storageType === 'sd' && (
|
||||||
<div className="flex animate-fadeIn justify-between gap-2 opacity-0"
|
<div className="animate-fadeIn space-y-2 opacity-0"
|
||||||
style={{ animationDuration: "0.7s", animationDelay: "0.25s" }}
|
style={{ animationDuration: "0.7s", animationDelay: "0.25s" }}
|
||||||
>
|
>
|
||||||
<AntdButton
|
<div className="w-full space-y-2">
|
||||||
disabled={loading}
|
<p className="w-full text-left text-xs text-slate-700 dark:text-slate-300">
|
||||||
type="primary"
|
{$at("Choose the file system for MicroSD formatting")}
|
||||||
danger={true}
|
</p>
|
||||||
onClick={handleFormatSDStorage}
|
<select
|
||||||
className="w-full text-red-500 dark:text-red-400 border-red-200 dark:border-red-800"
|
value={fsType}
|
||||||
>{$at("Format MicroSD Card")}</AntdButton>
|
onChange={(e) => setFsType(e.target.value as 'exfat' | 'fat32')}
|
||||||
|
style={{ width: "100%", padding: "8px", borderRadius: "4px" }}
|
||||||
|
>
|
||||||
|
<option value="fat32">FAT32</option>
|
||||||
|
<option value="exfat">exFAT</option>
|
||||||
|
</select>
|
||||||
|
<AntdButton
|
||||||
|
disabled={loading}
|
||||||
|
type="primary"
|
||||||
|
danger={true}
|
||||||
|
onClick={handleFormatSDStorage}
|
||||||
|
className="w-full text-red-500 dark:text-red-400 border-red-200 dark:border-red-800"
|
||||||
|
>{$at("Format MicroSD Card")} ({fsType})</AntdButton>
|
||||||
|
</div>
|
||||||
<AntdButton
|
<AntdButton
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|||||||
@@ -782,7 +782,11 @@ func rpcUnmountSDStorage() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func rpcFormatSDStorage(confirm bool) error {
|
func rpcFormatSDStorage(confirm bool, fsType string) error {
|
||||||
|
validFsTypes := map[string]bool{"exfat": true, "fat32": true}
|
||||||
|
if !validFsTypes[fsType] {
|
||||||
|
fsType = "fat32"
|
||||||
|
}
|
||||||
if !confirm {
|
if !confirm {
|
||||||
return fmt.Errorf("format not confirmed")
|
return fmt.Errorf("format not confirmed")
|
||||||
}
|
}
|
||||||
@@ -864,7 +868,13 @@ func rpcFormatSDStorage(confirm bool) error {
|
|||||||
return fmt.Errorf("failed to stat sd partition: %w", err)
|
return fmt.Errorf("failed to stat sd partition: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mkfsOut, mkfsErr := exec.Command("mkfs.vfat", "-F", "32", "-n", "PICOKVM", "/dev/mmcblk1p1").CombinedOutput()
|
var mkfsCmd *exec.Cmd
|
||||||
|
if fsType == "exfat" {
|
||||||
|
mkfsCmd = exec.Command("mkfs.exfat", "-n", "PICOKVM", "/dev/mmcblk1p1")
|
||||||
|
} else {
|
||||||
|
mkfsCmd = exec.Command("mkfs.vfat", "-F", "32", "-n", "PICOKVM", "/dev/mmcblk1p1")
|
||||||
|
}
|
||||||
|
mkfsOut, mkfsErr := mkfsCmd.CombinedOutput()
|
||||||
if mkfsErr != nil {
|
if mkfsErr != nil {
|
||||||
return fmt.Errorf("failed to format sdcard: %w: %s", mkfsErr, strings.TrimSpace(string(mkfsOut)))
|
return fmt.Errorf("failed to format sdcard: %w: %s", mkfsErr, strings.TrimSpace(string(mkfsOut)))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user