mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
feat: sftp perm setting & path copy (#467)
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"paste": "貼り付け",
|
||||
"path": "パス",
|
||||
"percentOfSize": "{size} の {percent}%",
|
||||
"permission": "権限",
|
||||
"pickFile": "ファイルを選択",
|
||||
"pingAvg": "平均:",
|
||||
"pingInputIP": "対象のIPまたはドメインを入力してください",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"paste": "вставить",
|
||||
"path": "путь",
|
||||
"percentOfSize": "{percent}% от {size}",
|
||||
"permission": "Разрешения",
|
||||
"pickFile": "выбрать файл",
|
||||
"pingAvg": "Среднее:",
|
||||
"pingInputIP": "Пожалуйста, введите целевой IP или доменное имя",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"paste": "粘贴",
|
||||
"path": "路径",
|
||||
"percentOfSize": "{size} 的 {percent}%",
|
||||
"permission": "权限",
|
||||
"pickFile": "选择文件",
|
||||
"pingAvg": "平均:",
|
||||
"pingInputIP": "请输入目标IP或域名",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"paste": "貼上",
|
||||
"path": "路徑",
|
||||
"percentOfSize": "{size} 的 {percent}%",
|
||||
"permission": "權限",
|
||||
"pickFile": "選擇檔案",
|
||||
"pingAvg": "平均:",
|
||||
"pingInputIP": "請輸入目標 IP 或域名",
|
||||
|
||||
@@ -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([
|
||||
|
||||
128
lib/view/widget/unix_perm.dart
Normal file
128
lib/view/widget/unix_perm.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user