feat: sftp perm setting & path copy (#467)

This commit is contained in:
lollipopkit🏳️‍⚧️
2024-07-18 21:40:40 +08:00
committed by GitHub
parent 5ee98f90e8
commit 076082c945
14 changed files with 248 additions and 48 deletions

View File

@@ -1,4 +1,5 @@
import 'package:dartssh2/dartssh2.dart';
import 'package:server_box/view/widget/unix_perm.dart';
extension SftpFileX on SftpFileMode {
String get str {
@@ -8,6 +9,26 @@ extension SftpFileX on SftpFileMode {
return '$user$group$other';
}
UnixPerm toUnixPerm() {
return UnixPerm(
user: RWX(
r: userRead,
w: userWrite,
x: userExecute,
),
group: RWX(
r: groupRead,
w: groupWrite,
x: groupExecute,
),
other: RWX(
r: otherRead,
w: otherWrite,
x: otherExecute,
),
);
}
}
String _getRoleMode(bool r, bool w, bool x) {

View File

@@ -209,6 +209,7 @@
"paste": "Einfügen",
"path": "Pfad",
"percentOfSize": "{percent}% von {size}",
"permission": "Berechtigungen",
"pickFile": "Datei wählen",
"pingAvg": "Avg:",
"pingInputIP": "Bitte gib eine Ziel-IP/Domain ein.",

View File

@@ -209,6 +209,7 @@
"paste": "Paste",
"path": "Path",
"percentOfSize": "{percent}% of {size}",
"permission": "Permissions",
"pickFile": "Pick file",
"pingAvg": "Avg:",
"pingInputIP": "Please input a target IP / domain.",

View File

@@ -209,6 +209,7 @@
"paste": "Pegar",
"path": "Ruta",
"percentOfSize": "El {percent}% de {size}",
"permission": "Permisos",
"pickFile": "Seleccionar archivo",
"pingAvg": "Promedio:",
"pingInputIP": "Por favor, introduce la IP de destino o el dominio",

View File

@@ -209,6 +209,7 @@
"paste": "Coller",
"path": "Chemin",
"percentOfSize": "{percent}% de {size}",
"permission": "Permissions",
"pickFile": "Choisir un fichier",
"pingAvg": "Moy.:",
"pingInputIP": "Veuillez saisir une adresse IP / un domaine cible.",

View File

@@ -209,6 +209,7 @@
"paste": "Tempel",
"path": "Jalur",
"percentOfSize": "{percent}% dari {size}",
"permission": "Izin",
"pickFile": "Pilih file",
"pingAvg": "Rata -rata:",
"pingInputIP": "Harap masukkan IP / domain target.",

View File

@@ -209,6 +209,7 @@
"paste": "貼り付け",
"path": "パス",
"percentOfSize": "{size} の {percent}%",
"permission": "権限",
"pickFile": "ファイルを選択",
"pingAvg": "平均:",
"pingInputIP": "対象のIPまたはドメインを入力してください",

View File

@@ -209,6 +209,7 @@
"paste": "Plakken",
"path": "Pad",
"percentOfSize": "{percent}% van {size}",
"permission": "Machtigingen",
"pickFile": "Bestand kiezen",
"pingAvg": "Gem:",
"pingInputIP": "Voer een doel-IP / domein in.",

View File

@@ -209,6 +209,7 @@
"paste": "Colar",
"path": "Caminho",
"percentOfSize": "{percent}% de {size}",
"permission": "Permissões",
"pickFile": "Escolher arquivo",
"pingAvg": "Média:",
"pingInputIP": "Por favor, insira o IP ou domínio alvo",

View File

@@ -209,6 +209,7 @@
"paste": "вставить",
"path": "путь",
"percentOfSize": "{percent}% от {size}",
"permission": "Разрешения",
"pickFile": "выбрать файл",
"pingAvg": "Среднее:",
"pingInputIP": "Пожалуйста, введите целевой IP или доменное имя",

View File

@@ -209,6 +209,7 @@
"paste": "粘贴",
"path": "路径",
"percentOfSize": "{size} 的 {percent}%",
"permission": "权限",
"pickFile": "选择文件",
"pingAvg": "平均:",
"pingInputIP": "请输入目标IP或域名",

View File

@@ -209,6 +209,7 @@
"paste": "貼上",
"path": "路徑",
"percentOfSize": "{size} 的 {percent}%",
"permission": "權限",
"pickFile": "選擇檔案",
"pingAvg": "平均:",
"pingInputIP": "請輸入目標 IP 或域名",

View File

@@ -5,18 +5,20 @@ import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart';
import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/core/extension/sftpfile.dart';
import 'package:server_box/core/route.dart';
import 'package:server_box/core/utils/comparator.dart';
import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/model/sftp/absolute_path.dart';
import 'package:server_box/data/model/sftp/browser_status.dart';
import 'package:server_box/data/model/sftp/req.dart';
import 'package:server_box/data/res/misc.dart';
import 'package:server_box/data/res/provider.dart';
import 'package:server_box/data/res/store.dart';
import 'package:server_box/view/widget/omit_start_text.dart';
import '../../../core/route.dart';
import '../../../data/model/server/server_private_info.dart';
import '../../../data/model/sftp/absolute_path.dart';
import '../../../data/model/sftp/browser_status.dart';
import '../../../data/model/sftp/req.dart';
import '../../widget/two_line_text.dart';
import 'package:icons_plus/icons_plus.dart';
import 'package:server_box/view/widget/two_line_text.dart';
import 'package:server_box/view/widget/unix_perm.dart';
class SftpPage extends StatefulWidget {
final ServerPrivateInfo spi;
@@ -216,7 +218,8 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
SftpReq(widget.spi, remotePath, path, SftpReqType.upload),
);
},
icon: const Icon(Icons.upload_file));
icon: const Icon(Icons.upload_file),
);
}
Widget _buildAddBtn() {
@@ -371,6 +374,43 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
title: Text(l10n.rename),
onTap: () => _rename(file),
),
ListTile(
leading: const Icon(MingCute.copy_line),
title: Text(l10n.copyPath),
onTap: () {
Pfs.copy(_getRemotePath(file));
context.pop();
context.showSnackBar(l10n.success);
},
),
ListTile(
leading: const Icon(Icons.security),
title: Text(l10n.permission),
onTap: () async {
context.pop();
final perm = file.attr.mode?.toUnixPerm() ?? UnixPerm.empty;
var newPerm = perm.copyWith();
final ok = await context.showRoundDialog(
child: UnixPermEditor(perm: perm, onChanged: (p) => newPerm = p),
actions: Btns.oks(onTap: () => context.pop(true)),
);
final permStr = newPerm.perm;
if (ok == true && permStr != perm.perm) {
print('${perm.perm} -> $permStr');
await context.showLoadingDialog(
fn: () async {
await _client!.run('chmod $permStr "${_getRemotePath(file)}"');
await _listDir();
},
onErr: (e, s) {
context.showErrDialog(e: e, s: s, operation: l10n.permission);
},
);
}
},
),
];
if (notDir) {
children.addAll([

View File

@@ -0,0 +1,128 @@
import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart';
final class RWX {
final bool r;
final bool w;
final bool x;
const RWX({
required this.r,
required this.w,
required this.x,
});
RWX copyWith({bool? r, bool? w, bool? x}) {
return RWX(r: r ?? this.r, w: w ?? this.w, x: x ?? this.x);
}
int get value {
return (r ? 4 : 0) + (w ? 2 : 0) + (x ? 1 : 0);
}
}
final class UnixPerm {
final RWX user;
final RWX group;
final RWX other;
const UnixPerm({
required this.user,
required this.group,
required this.other,
});
UnixPerm copyWith({RWX? user, RWX? group, RWX? other}) {
return UnixPerm(
user: user ?? this.user,
group: group ?? this.group,
other: other ?? this.other,
);
}
/// eg.: 744
String get perm {
return '${user.value}${group.value}${other.value}';
}
static UnixPerm get empty => const UnixPerm(
user: RWX(r: false, w: false, x: false),
group: RWX(r: false, w: false, x: false),
other: RWX(r: false, w: false, x: false),
);
}
final class UnixPermEditor extends StatefulWidget {
final UnixPerm perm;
final void Function(UnixPerm) onChanged;
const UnixPermEditor(
{super.key, required this.perm, required this.onChanged});
@override
_UnixPermEditorState createState() => _UnixPermEditorState();
}
final class _UnixPermEditorState extends State<UnixPermEditor> {
late UnixPerm perm;
@override
void initState() {
super.initState();
perm = widget.perm;
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text('R'),
Text('W'),
Text('X'),
],
).paddingOnly(left: 13),
UIs.height7,
_buildRow('U', perm.user),
_buildRow('G', perm.group),
_buildRow('O', perm.other),
],
);
}
Widget _buildRow(String title, RWX rwx) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(width: 7, child: Text(title)),
_buildSwitch(rwx.r, (v) {
setState(() {
perm = perm.copyWith(user: rwx.copyWith(r: v));
});
widget.onChanged(perm);
}),
_buildSwitch(rwx.w, (v) {
setState(() {
perm = perm.copyWith(user: rwx.copyWith(w: v));
});
widget.onChanged(perm);
}),
_buildSwitch(rwx.x, (v) {
setState(() {
perm = perm.copyWith(user: rwx.copyWith(x: v));
});
widget.onChanged(perm);
}),
],
);
}
Widget _buildSwitch(bool value, void Function(bool) onChanged) {
return Switch(
value: value,
onChanged: onChanged,
);
}
}