[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
172
components/sftp/SftpPermissionsDialog.tsx
Normal file
172
components/sftp/SftpPermissionsDialog.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* SFTP Permissions Editor Dialog
|
||||
*/
|
||||
|
||||
import React, { memo, useEffect, useState } from 'react';
|
||||
import { useI18n } from '../../application/i18n/I18nProvider';
|
||||
import { SftpFileEntry } from '../../types';
|
||||
import { Button } from '../ui/button';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog';
|
||||
|
||||
interface SftpPermissionsDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
file: SftpFileEntry | null;
|
||||
onSave: (file: SftpFileEntry, permissions: string) => void;
|
||||
}
|
||||
|
||||
const SftpPermissionsDialogInner: React.FC<SftpPermissionsDialogProps> = ({ open, onOpenChange, file, onSave }) => {
|
||||
const { t } = useI18n();
|
||||
const [permissions, setPermissions] = useState({
|
||||
owner: { read: false, write: false, execute: false },
|
||||
group: { read: false, write: false, execute: false },
|
||||
others: { read: false, write: false, execute: false },
|
||||
});
|
||||
|
||||
// Parse permissions from file
|
||||
// Supports both symbolic format (rwxr-xr-x) and octal format (755)
|
||||
useEffect(() => {
|
||||
if (file?.permissions) {
|
||||
const perms = file.permissions;
|
||||
|
||||
// Check if it's octal format (e.g., "755", "644")
|
||||
if (/^[0-7]{3,4}$/.test(perms)) {
|
||||
const octal = perms.length === 4 ? perms.slice(1) : perms;
|
||||
const ownerBits = parseInt(octal[0], 10);
|
||||
const groupBits = parseInt(octal[1], 10);
|
||||
const othersBits = parseInt(octal[2], 10);
|
||||
setPermissions({
|
||||
owner: {
|
||||
read: (ownerBits & 4) !== 0,
|
||||
write: (ownerBits & 2) !== 0,
|
||||
execute: (ownerBits & 1) !== 0,
|
||||
},
|
||||
group: {
|
||||
read: (groupBits & 4) !== 0,
|
||||
write: (groupBits & 2) !== 0,
|
||||
execute: (groupBits & 1) !== 0,
|
||||
},
|
||||
others: {
|
||||
read: (othersBits & 4) !== 0,
|
||||
write: (othersBits & 2) !== 0,
|
||||
execute: (othersBits & 1) !== 0,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse symbolic rwxrwxrwx format (skip first char for type)
|
||||
const pStr = perms.length === 10 ? perms.slice(1) : perms;
|
||||
if (pStr.length >= 9) {
|
||||
setPermissions({
|
||||
owner: {
|
||||
read: pStr[0] === 'r',
|
||||
write: pStr[1] === 'w',
|
||||
execute: pStr[2] === 'x' || pStr[2] === 's',
|
||||
},
|
||||
group: {
|
||||
read: pStr[3] === 'r',
|
||||
write: pStr[4] === 'w',
|
||||
execute: pStr[5] === 'x' || pStr[5] === 's',
|
||||
},
|
||||
others: {
|
||||
read: pStr[6] === 'r',
|
||||
write: pStr[7] === 'w',
|
||||
execute: pStr[8] === 'x' || pStr[8] === 't',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [file]);
|
||||
|
||||
const togglePerm = (role: 'owner' | 'group' | 'others', perm: 'read' | 'write' | 'execute') => {
|
||||
setPermissions(prev => ({
|
||||
...prev,
|
||||
[role]: { ...prev[role], [perm]: !prev[role][perm] }
|
||||
}));
|
||||
};
|
||||
|
||||
const getOctalPermissions = (): string => {
|
||||
const getNum = (p: { read: boolean; write: boolean; execute: boolean }) =>
|
||||
(p.read ? 4 : 0) + (p.write ? 2 : 0) + (p.execute ? 1 : 0);
|
||||
return `${getNum(permissions.owner)}${getNum(permissions.group)}${getNum(permissions.others)}`;
|
||||
};
|
||||
|
||||
const getSymbolicPermissions = (): string => {
|
||||
const getSym = (p: { read: boolean; write: boolean; execute: boolean }) =>
|
||||
`${p.read ? 'r' : '-'}${p.write ? 'w' : '-'}${p.execute ? 'x' : '-'}`;
|
||||
return getSym(permissions.owner) + getSym(permissions.group) + getSym(permissions.others);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (file) {
|
||||
onSave(file, getOctalPermissions());
|
||||
onOpenChange(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!file) return null;
|
||||
|
||||
const permLabel = (perm: 'read' | 'write' | 'execute') => (perm === 'read' ? 'R' : perm === 'write' ? 'W' : 'X');
|
||||
|
||||
const PermRow = ({ role, label }: { role: 'owner' | 'group' | 'others'; label: string }) => (
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-16 text-sm font-medium">{label}</div>
|
||||
<div className="flex gap-3">
|
||||
{(['read', 'write', 'execute'] as const).map(perm => (
|
||||
<label key={perm} className="flex items-center gap-1.5 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={permissions[role][perm]}
|
||||
onChange={() => togglePerm(role, perm)}
|
||||
className="rounded border-border"
|
||||
/>
|
||||
<span className="text-xs">{permLabel(perm)}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[400px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('sftp.permissions.title')}</DialogTitle>
|
||||
<DialogDescription className="truncate">
|
||||
{file.name}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-3">
|
||||
<PermRow role="owner" label={t('sftp.permissions.owner')} />
|
||||
<PermRow role="group" label={t('sftp.permissions.group')} />
|
||||
<PermRow role="others" label={t('sftp.permissions.others')} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-2 border-t border-border/60">
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{t('sftp.permissions.octal')}: <span className="font-mono text-foreground">{getOctalPermissions()}</span>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{t('sftp.permissions.symbolic')}: <span className="font-mono text-foreground">{getSymbolicPermissions()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleSave}>
|
||||
{t('common.apply')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export const SftpPermissionsDialog = memo(SftpPermissionsDialogInner);
|
||||
SftpPermissionsDialog.displayName = 'SftpPermissionsDialog';
|
||||
Reference in New Issue
Block a user