Add FieldUmask and use for `newFileMode` (#214)

pull/373/head
Kelvin Schoofs 3 years ago
parent af764380a6
commit b904fb9f17

@ -3,6 +3,9 @@
## Unreleased
### New features
- The settings UI now has a table-like field to modify `newFileMode` (#214)
### Changes
- The default `newFileMode` is now `0o664` instead defaulting to the underlying library's `0o666` (#214)
- **This changes the permission for newly created files**, defaulting to `rw-rw-r--` instead of `rw-rw-rw-`

@ -5,6 +5,7 @@ import { FieldDropdownWithInput } from '../FieldTypes/dropdownwithinput';
import { FieldNumber } from '../FieldTypes/number';
import { FieldPath } from '../FieldTypes/path';
import { FieldString } from '../FieldTypes/string';
import { FieldUmask } from '../FieldTypes/umask';
import { FileSystemConfig, invalidConfigName } from '../types/fileSystemConfig';
import FieldConfigGroup from './configGroupField';
import { PROXY_FIELD } from './proxyFields';
@ -121,6 +122,13 @@ export function agentForward(config: FileSystemConfig, onChange: FSCChanged<'age
return <FieldCheckbox key="agentForward" label="Forward agent" value={!!config.agentForward} onChange={callback} description={description} postface={postface} />;
}
export function newFileMode(config: FileSystemConfig, onChange: FSCChanged<'newFileMode'>): React.ReactElement {
const callback = (newValue?: number) => onChange('newFileMode', Number.isInteger(newValue) ? `0o${newValue!.toString(8)}` : undefined);
const description = 'The filemode to assign to new files created using VS Code, not the terminal. Similar to umask. Defaults to `rw-rw-r--` (regardless of server config, whether you are root, ...)';
const value = Number.isInteger(Number(config.newFileMode)) ? Number(config.newFileMode) : 0o664;
return <FieldUmask key="newFileMode" label="New file mode" value={value} onChange={callback} description={description} optional />;
}
export function sftpCommand(config: FileSystemConfig, onChange: FSCChanged<'sftpCommand'>): React.ReactElement {
const callback = (newValue?: string) => onChange('sftpCommand', newValue);
const description = 'A command to run on the remote SSH session to start a SFTP session (defaults to sftp subsystem)';
@ -157,5 +165,5 @@ export type FieldFactory = (config: FileSystemConfig, onChange: FSCChanged, onCh
export const FIELDS: FieldFactory[] = [
name, merge, label, group, putty, host, port,
root, agent, username, password, privateKeyPath, passphrase,
agentForward, sftpCommand, sftpSudo, terminalCommand, taskCommand,
newFileMode, agentForward, sftpCommand, sftpSudo, terminalCommand, taskCommand,
PROXY_FIELD];

@ -0,0 +1,79 @@
import { FieldBase } from './base';
const PERM_MAP: Record<number, string> = {
0: '---',
1: '--x',
2: '-w-',
3: '-wx',
4: 'r--',
5: 'r-x',
6: 'rw-',
7: 'rwx',
};
export class FieldUmask extends FieldBase<number | undefined> {
protected getValueClassName() {
return super.getValueClassName() + ' checkbox-box checkbox-small';
}
public renderInput() {
const value = this.getValue() || 0;
console.log(this.state.newValue, '=>', value);
const cell = (target: number, permission: number) => {
const shifted = permission * target;
const checked = (value & shifted) > 0;
return <td className="checkbox">
<div className="checkbox-box">
<div className={`checkbox ${checked ? 'checked' : ''}`}
onClick={() => this.setPart(shifted, !checked)} />
</div>
</td>;
};
const row = (target: number, name: string) => <tr>
<th scope="row">{name}</th>
{cell(target, 4)}
{cell(target, 2)}
{cell(target, 1)}
</tr>;
return <>
<table>
<tbody>
<tr><th /><th>Read</th><th>Write</th><th>Execute</th></tr>
{row(0o100, 'Owner')}
{row(0o010, 'Group')}
{row(0o001, 'Other')}
</tbody>
</table>
<span>
Resulting mask:{" "}
<code>
{PERM_MAP[(value / 0o100) & 0o7]}
{PERM_MAP[(value / 0o010) & 0o7]}
{PERM_MAP[(value / 0o001) & 0o7]}
</code>
</span>
</>;
}
public getError() {
const { newValue } = this.state;
const { validator, optional } = this.props;
if (newValue === undefined) {
if (optional) return null;
return 'No value given';
} else if (Number.isNaN(Number(newValue))) {
return 'Not a number';
}
return validator ? validator(newValue!) : null;
}
public getValue(): number | undefined {
const { newValue, oldValue } = this.state;
if (newValue === undefined) {
return this.props.optional ? newValue : Number(oldValue);
}
return typeof newValue === 'number' ? newValue : (Number(newValue) || undefined);
}
public setPart(part: number, checked: boolean): void {
this.setState(({ newValue }) => ({
newValue: checked ? (newValue || 0) | part : (newValue || 0) & ~part
}), () => this.props.onChange(this.getValue()));
}
}
Loading…
Cancel
Save