mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
opt.: sftp home & back (#533)
This commit is contained in:
@@ -30,14 +30,19 @@ linter:
|
|||||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
# producing the lint.
|
# producing the lint.
|
||||||
rules:
|
rules:
|
||||||
library_private_types_in_public_api: false
|
library_private_types_in_public_api: true
|
||||||
use_build_context_synchronously: false
|
use_build_context_synchronously: false
|
||||||
depend_on_referenced_packages: false
|
depend_on_referenced_packages: false
|
||||||
prefer_final_locals: true
|
prefer_final_locals: true
|
||||||
unnecessary_parenthesis: true
|
unnecessary_parenthesis: true
|
||||||
implicit_call_tearoffs: true
|
implicit_call_tearoffs: true
|
||||||
|
always_declare_return_types: true
|
||||||
|
always_use_package_imports: true
|
||||||
|
annotate_overrides: true
|
||||||
|
avoid_empty_else: true
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
avoid_return_types_on_setters: true
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Additional information about this file can be found at
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import 'package:dartssh2/dartssh2.dart';
|
|||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import '../../data/res/misc.dart';
|
import 'package:server_box/data/res/misc.dart';
|
||||||
|
|
||||||
typedef _OnStdout = void Function(String data, SSHSession session);
|
typedef OnStdout = void Function(String data, SSHSession session);
|
||||||
typedef _OnStdin = void Function(SSHSession session);
|
typedef OnStdin = void Function(SSHSession session);
|
||||||
|
|
||||||
typedef PwdRequestFunc = Future<String?> Function(String? user);
|
typedef PwdRequestFunc = Future<String?> Function(String? user);
|
||||||
|
|
||||||
@@ -16,15 +16,15 @@ extension SSHClientX on SSHClient {
|
|||||||
/// TODO: delete [exec]
|
/// TODO: delete [exec]
|
||||||
Future<SSHSession> exec(
|
Future<SSHSession> exec(
|
||||||
String cmd, {
|
String cmd, {
|
||||||
_OnStdout? onStderr,
|
OnStdout? onStderr,
|
||||||
_OnStdout? onStdout,
|
OnStdout? onStdout,
|
||||||
_OnStdin? stdin,
|
OnStdin? stdin,
|
||||||
bool redirectToBash = false, // not working yet. do not use
|
bool redirectToBash = false, // not working yet. do not use
|
||||||
}) async {
|
}) async {
|
||||||
final session = await execute(redirectToBash ? "head -1 | bash" : cmd);
|
final session = await execute(redirectToBash ? 'head -1 | bash' : cmd);
|
||||||
|
|
||||||
if (redirectToBash) {
|
if (redirectToBash) {
|
||||||
session.stdin.add("$cmd\n".uint8List);
|
session.stdin.add('$cmd\n'.uint8List);
|
||||||
}
|
}
|
||||||
|
|
||||||
final stdoutDone = Completer<void>();
|
final stdoutDone = Completer<void>();
|
||||||
@@ -62,9 +62,9 @@ extension SSHClientX on SSHClient {
|
|||||||
Future<int?> execWithPwd(
|
Future<int?> execWithPwd(
|
||||||
String cmd, {
|
String cmd, {
|
||||||
BuildContext? context,
|
BuildContext? context,
|
||||||
_OnStdout? onStdout,
|
OnStdout? onStdout,
|
||||||
_OnStdout? onStderr,
|
OnStdout? onStderr,
|
||||||
_OnStdin? stdin,
|
OnStdin? stdin,
|
||||||
bool redirectToBash = false, // not working yet. do not use
|
bool redirectToBash = false, // not working yet. do not use
|
||||||
required String id,
|
required String id,
|
||||||
}) async {
|
}) async {
|
||||||
|
|||||||
@@ -20,18 +20,18 @@ import 'package:server_box/view/page/ssh/page.dart';
|
|||||||
import 'package:server_box/view/page/setting/seq/virt_key.dart';
|
import 'package:server_box/view/page/setting/seq/virt_key.dart';
|
||||||
import 'package:server_box/view/page/storage/local.dart';
|
import 'package:server_box/view/page/storage/local.dart';
|
||||||
|
|
||||||
import '../data/model/server/snippet.dart';
|
import 'package:server_box/data/model/server/snippet.dart';
|
||||||
import '../view/page/editor.dart';
|
import 'package:server_box/view/page/editor.dart';
|
||||||
import '../view/page/process.dart';
|
import 'package:server_box/view/page/process.dart';
|
||||||
import '../view/page/server/edit.dart';
|
import 'package:server_box/view/page/server/edit.dart';
|
||||||
import '../view/page/server/tab.dart';
|
import 'package:server_box/view/page/server/tab.dart';
|
||||||
import '../view/page/setting/entry.dart';
|
import 'package:server_box/view/page/setting/entry.dart';
|
||||||
import '../view/page/setting/seq/srv_detail_seq.dart';
|
import 'package:server_box/view/page/setting/seq/srv_detail_seq.dart';
|
||||||
import '../view/page/setting/seq/srv_seq.dart';
|
import 'package:server_box/view/page/setting/seq/srv_seq.dart';
|
||||||
import '../view/page/snippet/edit.dart';
|
import 'package:server_box/view/page/snippet/edit.dart';
|
||||||
import '../view/page/snippet/list.dart';
|
import 'package:server_box/view/page/snippet/list.dart';
|
||||||
import '../view/page/storage/sftp.dart';
|
import 'package:server_box/view/page/storage/sftp.dart';
|
||||||
import '../view/page/storage/sftp_mission.dart';
|
import 'package:server_box/view/page/storage/sftp_mission.dart';
|
||||||
|
|
||||||
class AppRoutes {
|
class AppRoutes {
|
||||||
final Widget page;
|
final Widget page;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:server_box/data/model/app/error.dart';
|
import 'package:server_box/data/model/app/error.dart';
|
||||||
import 'package:server_box/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
|
||||||
import '../../data/model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
|
|
||||||
/// Must put this func out of any Class.
|
/// Must put this func out of any Class.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import 'package:server_box/data/model/app/backup.dart';
|
|||||||
import 'package:server_box/data/model/app/sync.dart';
|
import 'package:server_box/data/model/app/sync.dart';
|
||||||
import 'package:server_box/data/res/misc.dart';
|
import 'package:server_box/data/res/misc.dart';
|
||||||
|
|
||||||
import '../../../data/model/app/error.dart';
|
import 'package:server_box/data/model/app/error.dart';
|
||||||
|
|
||||||
abstract final class ICloud {
|
abstract final class ICloud {
|
||||||
static const _containerId = 'iCloud.tech.lolli.serverbox';
|
static const _containerId = 'iCloud.tech.lolli.serverbox';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:server_box/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
|
|
||||||
import '../../res/build_data.dart';
|
import 'package:server_box/data/res/build_data.dart';
|
||||||
import '../server/system.dart';
|
import 'package:server_box/data/model/server/system.dart';
|
||||||
|
|
||||||
enum ShellFunc {
|
enum ShellFunc {
|
||||||
status,
|
status,
|
||||||
@@ -47,11 +47,11 @@ enum ShellFunc {
|
|||||||
static String getInstallShellCmd(String id) {
|
static String getInstallShellCmd(String id) {
|
||||||
final scriptDir = getScriptDir(id);
|
final scriptDir = getScriptDir(id);
|
||||||
final scriptPath = '$scriptDir/$scriptFile';
|
final scriptPath = '$scriptDir/$scriptFile';
|
||||||
return """
|
return '''
|
||||||
mkdir -p $scriptDir
|
mkdir -p $scriptDir
|
||||||
cat > $scriptPath
|
cat > $scriptPath
|
||||||
chmod 744 $scriptPath
|
chmod 744 $scriptPath
|
||||||
""";
|
''';
|
||||||
}
|
}
|
||||||
|
|
||||||
String get flag => switch (this) {
|
String get flag => switch (this) {
|
||||||
|
|||||||
@@ -45,21 +45,21 @@ final class PodmanImg implements ContainerImg {
|
|||||||
String toRawJson() => json.encode(toJson());
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
factory PodmanImg.fromJson(Map<String, dynamic> json) => PodmanImg(
|
factory PodmanImg.fromJson(Map<String, dynamic> json) => PodmanImg(
|
||||||
repository: json["repository"],
|
repository: json['repository'],
|
||||||
tag: json["tag"],
|
tag: json['tag'],
|
||||||
id: json["Id"],
|
id: json['Id'],
|
||||||
created: json["Created"],
|
created: json['Created'],
|
||||||
size: json["Size"],
|
size: json['Size'],
|
||||||
containers: json["Containers"],
|
containers: json['Containers'],
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"repository": repository,
|
'repository': repository,
|
||||||
"tag": tag,
|
'tag': tag,
|
||||||
"Id": id,
|
'Id': id,
|
||||||
"Created": created,
|
'Created': created,
|
||||||
"Size": size,
|
'Size': size,
|
||||||
"Containers": containers,
|
'Containers': containers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,36 +96,36 @@ final class DockerImg implements ContainerImg {
|
|||||||
String toRawJson() => json.encode(toJson());
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
factory DockerImg.fromJson(Map<String, dynamic> json) {
|
factory DockerImg.fromJson(Map<String, dynamic> json) {
|
||||||
final containers = switch (json["Containers"]) {
|
final containers = switch (json['Containers']) {
|
||||||
final String a => a,
|
final String a => a,
|
||||||
final Object? a => a.toString(),
|
final Object? a => a.toString(),
|
||||||
};
|
};
|
||||||
final repo = switch (json["Repository"] ?? json["Names"]) {
|
final repo = switch (json['Repository'] ?? json['Names']) {
|
||||||
final String a => a,
|
final String a => a,
|
||||||
final List a => a.firstOrNull.toString(),
|
final List a => a.firstOrNull.toString(),
|
||||||
final Object? a => a.toString(),
|
final Object? a => a.toString(),
|
||||||
};
|
};
|
||||||
final size = switch (json["Size"]) {
|
final size = switch (json['Size']) {
|
||||||
final String a => a,
|
final String a => a,
|
||||||
final int a => a.bytes2Str,
|
final int a => a.bytes2Str,
|
||||||
final Object? a => a.toString(),
|
final Object? a => a.toString(),
|
||||||
};
|
};
|
||||||
return DockerImg(
|
return DockerImg(
|
||||||
containers: containers,
|
containers: containers,
|
||||||
createdAt: json["CreatedAt"],
|
createdAt: json['CreatedAt'],
|
||||||
id: json["ID"] ?? json["Id"] ?? '',
|
id: json['ID'] ?? json['Id'] ?? '',
|
||||||
repository: repo,
|
repository: repo,
|
||||||
size: size,
|
size: size,
|
||||||
tag: json["Tag"],
|
tag: json['Tag'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"Containers": containers,
|
'Containers': containers,
|
||||||
"CreatedAt": createdAt,
|
'CreatedAt': createdAt,
|
||||||
"ID": id,
|
'ID': id,
|
||||||
"Repository": repository,
|
'Repository': repository,
|
||||||
"Size": size,
|
'Size': size,
|
||||||
"Tag": tag,
|
'Tag': tag,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,29 +84,29 @@ final class PodmanPs implements ContainerPs {
|
|||||||
String toRawJson() => json.encode(toJson());
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
factory PodmanPs.fromJson(Map<String, dynamic> json) => PodmanPs(
|
factory PodmanPs.fromJson(Map<String, dynamic> json) => PodmanPs(
|
||||||
command: json["Command"] == null
|
command: json['Command'] == null
|
||||||
? []
|
? []
|
||||||
: List<String>.from(json["Command"]!.map((x) => x)),
|
: List<String>.from(json['Command']!.map((x) => x)),
|
||||||
created:
|
created:
|
||||||
json["Created"] == null ? null : DateTime.parse(json["Created"]),
|
json['Created'] == null ? null : DateTime.parse(json['Created']),
|
||||||
exited: json["Exited"],
|
exited: json['Exited'],
|
||||||
id: json["Id"],
|
id: json['Id'],
|
||||||
image: json["Image"],
|
image: json['Image'],
|
||||||
names: json["Names"] == null
|
names: json['Names'] == null
|
||||||
? []
|
? []
|
||||||
: List<String>.from(json["Names"]!.map((x) => x)),
|
: List<String>.from(json['Names']!.map((x) => x)),
|
||||||
startedAt: json["StartedAt"],
|
startedAt: json['StartedAt'],
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"Command":
|
'Command':
|
||||||
command == null ? [] : List<dynamic>.from(command!.map((x) => x)),
|
command == null ? [] : List<dynamic>.from(command!.map((x) => x)),
|
||||||
"Created": created?.toIso8601String(),
|
'Created': created?.toIso8601String(),
|
||||||
"Exited": exited,
|
'Exited': exited,
|
||||||
"Id": id,
|
'Id': id,
|
||||||
"Image": image,
|
'Image': image,
|
||||||
"Names": names == null ? [] : List<dynamic>.from(names!.map((x) => x)),
|
'Names': names == null ? [] : List<dynamic>.from(names!.map((x) => x)),
|
||||||
"StartedAt": startedAt,
|
'StartedAt': startedAt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ class UpgradePkgInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _parseApt(String raw) {
|
void _parseApt(String raw) {
|
||||||
final split1 = raw.split("/");
|
final split1 = raw.split('/');
|
||||||
package = split1[0];
|
package = split1[0];
|
||||||
final split2 = split1[1].split(" ");
|
final split2 = split1[1].split(' ');
|
||||||
newVersion = split2[1];
|
newVersion = split2[1];
|
||||||
arch = split2[2];
|
arch = split2[2];
|
||||||
nowVersion = split2[5].replaceFirst(']', '');
|
nowVersion = split2[5].replaceFirst(']', '');
|
||||||
@@ -53,7 +53,7 @@ class UpgradePkgInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _parseZypper(String raw) {
|
void _parseZypper(String raw) {
|
||||||
final cols = raw.split("|");
|
final cols = raw.split('|');
|
||||||
package = cols[2].trim();
|
package = cols[2].trim();
|
||||||
nowVersion = cols[3].trim();
|
nowVersion = cols[3].trim();
|
||||||
newVersion = cols[4].trim();
|
newVersion = cols[4].trim();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import '../../res/misc.dart';
|
import 'package:server_box/data/res/misc.dart';
|
||||||
|
|
||||||
class Conn {
|
class Conn {
|
||||||
final int maxConn;
|
final int maxConn;
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ final class ServerCustom {
|
|||||||
|
|
||||||
static ServerCustom fromJson(Map<String, dynamic> json) {
|
static ServerCustom fromJson(Map<String, dynamic> json) {
|
||||||
//final temperature = json["temperature"] as String?;
|
//final temperature = json["temperature"] as String?;
|
||||||
final pveAddr = json["pveAddr"] as String?;
|
final pveAddr = json['pveAddr'] as String?;
|
||||||
final pveIgnoreCert = json["pveIgnoreCert"] as bool;
|
final pveIgnoreCert = json['pveIgnoreCert'] as bool;
|
||||||
final cmds = json["cmds"] as Map<String, dynamic>?;
|
final cmds = json['cmds'] as Map<String, dynamic>?;
|
||||||
final preferTempDev = json["preferTempDev"] as String?;
|
final preferTempDev = json['preferTempDev'] as String?;
|
||||||
final logoUrl = json["logoUrl"] as String?;
|
final logoUrl = json['logoUrl'] as String?;
|
||||||
return ServerCustom(
|
return ServerCustom(
|
||||||
//temperature: temperature,
|
//temperature: temperature,
|
||||||
pveAddr: pveAddr,
|
pveAddr: pveAddr,
|
||||||
@@ -51,18 +51,18 @@ final class ServerCustom {
|
|||||||
// json["temperature"] = temperature;
|
// json["temperature"] = temperature;
|
||||||
// }
|
// }
|
||||||
if (pveAddr != null) {
|
if (pveAddr != null) {
|
||||||
json["pveAddr"] = pveAddr;
|
json['pveAddr'] = pveAddr;
|
||||||
}
|
}
|
||||||
json["pveIgnoreCert"] = pveIgnoreCert;
|
json['pveIgnoreCert'] = pveIgnoreCert;
|
||||||
|
|
||||||
if (cmds != null) {
|
if (cmds != null) {
|
||||||
json["cmds"] = cmds;
|
json['cmds'] = cmds;
|
||||||
}
|
}
|
||||||
if (preferTempDev != null) {
|
if (preferTempDev != null) {
|
||||||
json["preferTempDev"] = preferTempDev;
|
json['preferTempDev'] = preferTempDev;
|
||||||
}
|
}
|
||||||
if (logoUrl != null) {
|
if (logoUrl != null) {
|
||||||
json["logoUrl"] = logoUrl;
|
json['logoUrl'] = logoUrl;
|
||||||
}
|
}
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:server_box/data/model/server/time_seq.dart';
|
import 'package:server_box/data/model/server/time_seq.dart';
|
||||||
|
|
||||||
import '../../res/misc.dart';
|
import 'package:server_box/data/res/misc.dart';
|
||||||
|
|
||||||
class Disk {
|
class Disk {
|
||||||
final String fs;
|
final String fs;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
|
||||||
import 'time_seq.dart';
|
import 'package:server_box/data/model/server/time_seq.dart';
|
||||||
|
|
||||||
class NetSpeedPart extends TimeSeqIface<NetSpeedPart> {
|
class NetSpeedPart extends TimeSeqIface<NetSpeedPart> {
|
||||||
final String device;
|
final String device;
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ class PrivateKeyInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PrivateKeyInfo.fromJson(Map<String, dynamic> json)
|
PrivateKeyInfo.fromJson(Map<String, dynamic> json)
|
||||||
: id = json["id"].toString(),
|
: id = json['id'].toString(),
|
||||||
key = json["private_key"].toString();
|
key = json['private_key'].toString();
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final data = <String, String>{};
|
final data = <String, String>{};
|
||||||
data["id"] = id;
|
data['id'] = id;
|
||||||
data["private_key"] = key;
|
data['private_key'] = key;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
|
||||||
import '../../../data/res/misc.dart';
|
import 'package:server_box/data/res/misc.dart';
|
||||||
|
|
||||||
class _ProcValIdxMap {
|
class _ProcValIdxMap {
|
||||||
final int pid;
|
final int pid;
|
||||||
@@ -58,7 +58,7 @@ class Proc {
|
|||||||
required this.command,
|
required this.command,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Proc.parse(String raw, _ProcValIdxMap map) {
|
factory Proc._parse(String raw, _ProcValIdxMap map) {
|
||||||
final parts = raw.split(RegExp(r'\s+'));
|
final parts = raw.split(RegExp(r'\s+'));
|
||||||
return Proc(
|
return Proc(
|
||||||
user: map.user == null ? null : parts[map.user!],
|
user: map.user == null ? null : parts[map.user!],
|
||||||
@@ -139,7 +139,7 @@ class PsResult {
|
|||||||
final line = lines[i];
|
final line = lines[i];
|
||||||
if (line.isEmpty) continue;
|
if (line.isEmpty) continue;
|
||||||
try {
|
try {
|
||||||
procs.add(Proc.parse(line, map));
|
procs.add(Proc._parse(line, map));
|
||||||
} catch (e, trace) {
|
} catch (e, trace) {
|
||||||
errs.add('$line: $e');
|
errs.add('$line: $e');
|
||||||
Loggers.app.warning('Process failed', e, trace);
|
Loggers.app.warning('Process failed', e, trace);
|
||||||
|
|||||||
@@ -90,5 +90,5 @@ enum ServerConn {
|
|||||||
/// Status parsing finished
|
/// Status parsing finished
|
||||||
finished;
|
finished;
|
||||||
|
|
||||||
operator <(ServerConn other) => index < other.index;
|
bool operator <(ServerConn other) => index < other.index;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:server_box/data/model/server/server.dart';
|
|||||||
import 'package:server_box/data/model/server/wol_cfg.dart';
|
import 'package:server_box/data/model/server/wol_cfg.dart';
|
||||||
import 'package:server_box/data/res/provider.dart';
|
import 'package:server_box/data/res/provider.dart';
|
||||||
|
|
||||||
import '../app/error.dart';
|
import 'package:server_box/data/model/app/error.dart';
|
||||||
|
|
||||||
part 'server_private_info.g.dart';
|
part 'server_private_info.g.dart';
|
||||||
|
|
||||||
@@ -67,23 +67,23 @@ class ServerPrivateInfo {
|
|||||||
}) : id = '$user@$ip:$port';
|
}) : id = '$user@$ip:$port';
|
||||||
|
|
||||||
static ServerPrivateInfo fromJson(Map<String, dynamic> json) {
|
static ServerPrivateInfo fromJson(Map<String, dynamic> json) {
|
||||||
final ip = json["ip"] as String? ?? '';
|
final ip = json['ip'] as String? ?? '';
|
||||||
final port = json["port"] as int? ?? 22;
|
final port = json['port'] as int? ?? 22;
|
||||||
final user = json["user"] as String? ?? 'root';
|
final user = json['user'] as String? ?? 'root';
|
||||||
final name = json["name"] as String? ?? '';
|
final name = json['name'] as String? ?? '';
|
||||||
final pwd = json["pwd"] as String? ?? json["authorization"] as String?;
|
final pwd = json['pwd'] as String? ?? json['authorization'] as String?;
|
||||||
final keyId = json["pubKeyId"] as String?;
|
final keyId = json['pubKeyId'] as String?;
|
||||||
final tags = (json["tags"] as List?)?.cast<String>();
|
final tags = (json['tags'] as List?)?.cast<String>();
|
||||||
final alterUrl = json["alterUrl"] as String?;
|
final alterUrl = json['alterUrl'] as String?;
|
||||||
final autoConnect = json["autoConnect"] as bool?;
|
final autoConnect = json['autoConnect'] as bool?;
|
||||||
final jumpId = json["jumpId"] as String?;
|
final jumpId = json['jumpId'] as String?;
|
||||||
final custom = json["customCmd"] == null
|
final custom = json['customCmd'] == null
|
||||||
? null
|
? null
|
||||||
: ServerCustom.fromJson(json["custom"].cast<String, dynamic>());
|
: ServerCustom.fromJson(json['custom'].cast<String, dynamic>());
|
||||||
final wolCfg = json["wolCfg"] == null
|
final wolCfg = json['wolCfg'] == null
|
||||||
? null
|
? null
|
||||||
: WakeOnLanCfg.fromJson(json["wolCfg"].cast<String, dynamic>());
|
: WakeOnLanCfg.fromJson(json['wolCfg'].cast<String, dynamic>());
|
||||||
final envs_ = json["envs"] as Map<String, dynamic>?;
|
final envs_ = json['envs'] as Map<String, dynamic>?;
|
||||||
final envs = <String, String>{};
|
final envs = <String, String>{};
|
||||||
if (envs_ != null) {
|
if (envs_ != null) {
|
||||||
envs_.forEach((key, value) {
|
envs_.forEach((key, value) {
|
||||||
@@ -112,36 +112,36 @@ class ServerPrivateInfo {
|
|||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final Map<String, dynamic> data = <String, dynamic>{};
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
data["name"] = name;
|
data['name'] = name;
|
||||||
data["ip"] = ip;
|
data['ip'] = ip;
|
||||||
data["port"] = port;
|
data['port'] = port;
|
||||||
data["user"] = user;
|
data['user'] = user;
|
||||||
if (pwd != null) {
|
if (pwd != null) {
|
||||||
data["pwd"] = pwd;
|
data['pwd'] = pwd;
|
||||||
}
|
}
|
||||||
if (keyId != null) {
|
if (keyId != null) {
|
||||||
data["pubKeyId"] = keyId;
|
data['pubKeyId'] = keyId;
|
||||||
}
|
}
|
||||||
if (tags != null) {
|
if (tags != null) {
|
||||||
data["tags"] = tags;
|
data['tags'] = tags;
|
||||||
}
|
}
|
||||||
if (alterUrl != null) {
|
if (alterUrl != null) {
|
||||||
data["alterUrl"] = alterUrl;
|
data['alterUrl'] = alterUrl;
|
||||||
}
|
}
|
||||||
if (autoConnect != null) {
|
if (autoConnect != null) {
|
||||||
data["autoConnect"] = autoConnect;
|
data['autoConnect'] = autoConnect;
|
||||||
}
|
}
|
||||||
if (jumpId != null) {
|
if (jumpId != null) {
|
||||||
data["jumpId"] = jumpId;
|
data['jumpId'] = jumpId;
|
||||||
}
|
}
|
||||||
if (custom != null) {
|
if (custom != null) {
|
||||||
data["custom"] = custom?.toJson();
|
data['custom'] = custom?.toJson();
|
||||||
}
|
}
|
||||||
if (wolCfg != null) {
|
if (wolCfg != null) {
|
||||||
data["wolCfg"] = wolCfg?.toJson();
|
data['wolCfg'] = wolCfg?.toJson();
|
||||||
}
|
}
|
||||||
if (envs != null) {
|
if (envs != null) {
|
||||||
data["envs"] = envs;
|
data['envs'] = envs;
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,7 @@ class ServerPrivateInfo {
|
|||||||
custom?.cmds != old.custom?.cmds;
|
custom?.cmds != old.custom?.cmds;
|
||||||
}
|
}
|
||||||
|
|
||||||
_IpPort fromStringUrl() {
|
IpPort fromStringUrl() {
|
||||||
if (alterUrl == null) {
|
if (alterUrl == null) {
|
||||||
throw SSHErr(type: SSHErrType.connect, message: 'alterUrl is null');
|
throw SSHErr(type: SSHErrType.connect, message: 'alterUrl is null');
|
||||||
}
|
}
|
||||||
@@ -177,7 +177,7 @@ class ServerPrivateInfo {
|
|||||||
if (port <= 0 || port > 65535) {
|
if (port <= 0 || port > 65535) {
|
||||||
throw SSHErr(type: SSHErrType.connect, message: 'alterUrl port error');
|
throw SSHErr(type: SSHErrType.connect, message: 'alterUrl port error');
|
||||||
}
|
}
|
||||||
return _IpPort(ip_, port_);
|
return IpPort(ip_, port_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -208,9 +208,9 @@ class ServerPrivateInfo {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _IpPort {
|
class IpPort {
|
||||||
final String ip;
|
final String ip;
|
||||||
final int port;
|
final int port;
|
||||||
|
|
||||||
_IpPort(this.ip, this.port);
|
IpPort(this.ip, this.port);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import 'package:server_box/data/model/server/sensors.dart';
|
|||||||
import 'package:server_box/data/model/server/server.dart';
|
import 'package:server_box/data/model/server/server.dart';
|
||||||
import 'package:server_box/data/model/server/system.dart';
|
import 'package:server_box/data/model/server/system.dart';
|
||||||
|
|
||||||
import '../app/shell_func.dart';
|
import 'package:server_box/data/model/app/shell_func.dart';
|
||||||
import 'cpu.dart';
|
import 'package:server_box/data/model/server/cpu.dart';
|
||||||
import 'disk.dart';
|
import 'package:server_box/data/model/server/disk.dart';
|
||||||
import 'memory.dart';
|
import 'package:server_box/data/model/server/memory.dart';
|
||||||
import 'net_speed.dart';
|
import 'package:server_box/data/model/server/net_speed.dart';
|
||||||
import 'conn.dart';
|
import 'package:server_box/data/model/server/conn.dart';
|
||||||
|
|
||||||
class ServerStatusUpdateReq {
|
class ServerStatusUpdateReq {
|
||||||
final ServerStatus ss;
|
final ServerStatus ss;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import 'package:hive_flutter/hive_flutter.dart';
|
|||||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
import 'package:xterm/core.dart';
|
import 'package:xterm/core.dart';
|
||||||
|
|
||||||
import '../app/tag_pickable.dart';
|
import 'package:server_box/data/model/app/tag_pickable.dart';
|
||||||
|
|
||||||
part 'snippet.g.dart';
|
part 'snippet.g.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import 'package:fl_lib/fl_lib.dart';
|
|||||||
|
|
||||||
class AbsolutePath {
|
class AbsolutePath {
|
||||||
String _path;
|
String _path;
|
||||||
|
final _prePath = <String>[];
|
||||||
|
|
||||||
|
AbsolutePath(this._path);
|
||||||
|
|
||||||
String get path => _path;
|
String get path => _path;
|
||||||
final List<String> _prePath;
|
|
||||||
|
|
||||||
AbsolutePath(this._path) : _prePath = ['/'];
|
/// Update path, not set path
|
||||||
|
set path(String newPath) {
|
||||||
void update(String newPath) {
|
|
||||||
_prePath.add(_path);
|
_prePath.add(_path);
|
||||||
if (newPath == '..') {
|
if (newPath == '..') {
|
||||||
_path = _path.substring(0, _path.lastIndexOf('/'));
|
_path = _path.substring(0, _path.lastIndexOf('/'));
|
||||||
@@ -16,10 +18,6 @@ class AbsolutePath {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (newPath == '/') {
|
|
||||||
_path = '/';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (newPath.startsWith('/')) {
|
if (newPath.startsWith('/')) {
|
||||||
_path = newPath;
|
_path = newPath;
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import 'package:dartssh2/dartssh2.dart';
|
|||||||
import 'package:server_box/data/model/sftp/absolute_path.dart';
|
import 'package:server_box/data/model/sftp/absolute_path.dart';
|
||||||
|
|
||||||
class SftpBrowserStatus {
|
class SftpBrowserStatus {
|
||||||
List<SftpName>? files;
|
final List<SftpName> files = [];
|
||||||
AbsolutePath? path;
|
final AbsolutePath path = AbsolutePath('/');
|
||||||
SftpClient? client;
|
SftpClient? client;
|
||||||
|
|
||||||
SftpBrowserStatus();
|
SftpBrowserStatus(SSHClient client) {
|
||||||
|
client.sftp().then((value) => this.client = value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class ContainerProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final res = await client?.run(_wrap(ContainerCmdType.images.exec(type)));
|
final res = await client?.run(_wrap(ContainerCmdType.images.exec(type)));
|
||||||
if (res?.string.toLowerCase().contains("permission denied") ?? false) {
|
if (res?.string.toLowerCase().contains('permission denied') ?? false) {
|
||||||
return sudoCompleter.complete(true);
|
return sudoCompleter.complete(true);
|
||||||
}
|
}
|
||||||
return sudoCompleter.complete(false);
|
return sudoCompleter.complete(false);
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ final class PveProvider extends ChangeNotifier {
|
|||||||
socket.cast<List<int>>().pipe(forward.sink);
|
socket.cast<List<int>>().pipe(forward.sink);
|
||||||
});*/
|
});*/
|
||||||
|
|
||||||
if (url.isScheme("https")) {
|
if (url.isScheme('https')) {
|
||||||
return SecureSocket.startConnect('localhost', _localPort,
|
return SecureSocket.startConnect('localhost', _localPort,
|
||||||
onBadCertificate: (_) => true);
|
onBadCertificate: (_) => true);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ import 'package:server_box/data/model/server/system.dart';
|
|||||||
// import 'package:server_box/data/res/provider.dart';
|
// import 'package:server_box/data/res/provider.dart';
|
||||||
import 'package:server_box/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
|
||||||
import '../../core/utils/server.dart';
|
import 'package:server_box/core/utils/server.dart';
|
||||||
import '../model/server/server.dart';
|
import 'package:server_box/data/model/server/server.dart';
|
||||||
import '../model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
import '../model/server/server_status_update_req.dart';
|
import 'package:server_box/data/model/server/server_status_update_req.dart';
|
||||||
import '../model/server/try_limiter.dart';
|
import 'package:server_box/data/model/server/try_limiter.dart';
|
||||||
import '../res/status.dart';
|
import 'package:server_box/data/res/status.dart';
|
||||||
|
|
||||||
class ServerProvider extends ChangeNotifier {
|
class ServerProvider extends ChangeNotifier {
|
||||||
final Map<String, Server> _servers = {};
|
final Map<String, Server> _servers = {};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// This file is generated by fl_build. Do not edit.
|
// This file is generated by fl_build. Do not edit.
|
||||||
|
|
||||||
class BuildData {
|
class BuildData {
|
||||||
static const String name = "ServerBox";
|
static const String name = 'ServerBox';
|
||||||
static const int build = 1058;
|
static const int build = 1058;
|
||||||
static const int script = 56;
|
static const int script = 56;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import 'package:server_box/data/model/server/server.dart';
|
import 'package:server_box/data/model/server/server.dart';
|
||||||
import 'package:server_box/data/model/server/temp.dart';
|
import 'package:server_box/data/model/server/temp.dart';
|
||||||
|
|
||||||
import '../model/server/cpu.dart';
|
import 'package:server_box/data/model/server/cpu.dart';
|
||||||
import '../model/server/disk.dart';
|
import 'package:server_box/data/model/server/disk.dart';
|
||||||
import '../model/server/memory.dart';
|
import 'package:server_box/data/model/server/memory.dart';
|
||||||
import '../model/server/net_speed.dart';
|
import 'package:server_box/data/model/server/net_speed.dart';
|
||||||
import '../model/server/conn.dart';
|
import 'package:server_box/data/model/server/conn.dart';
|
||||||
import '../model/server/system.dart';
|
import 'package:server_box/data/model/server/system.dart';
|
||||||
|
|
||||||
abstract final class InitStatus {
|
abstract final class InitStatus {
|
||||||
static SingleCpuCore get _initOneTimeCpuStatus => SingleCpuCore(
|
static SingleCpuCore get _initOneTimeCpuStatus => SingleCpuCore(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
|
||||||
import '../model/server/private_key_info.dart';
|
import 'package:server_box/data/model/server/private_key_info.dart';
|
||||||
|
|
||||||
class PrivateKeyStore extends PersistentStore {
|
class PrivateKeyStore extends PersistentStore {
|
||||||
PrivateKeyStore() : super('key');
|
PrivateKeyStore() : super('key');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
|
||||||
import '../model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
|
|
||||||
class ServerStore extends PersistentStore {
|
class ServerStore extends PersistentStore {
|
||||||
ServerStore() : super('server');
|
ServerStore() : super('server');
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import 'package:server_box/data/model/app/menu/server_func.dart';
|
|||||||
import 'package:server_box/data/model/app/server_detail_card.dart';
|
import 'package:server_box/data/model/app/server_detail_card.dart';
|
||||||
import 'package:server_box/data/model/ssh/virtual_key.dart';
|
import 'package:server_box/data/model/ssh/virtual_key.dart';
|
||||||
|
|
||||||
import '../model/app/net_view.dart';
|
import 'package:server_box/data/model/app/net_view.dart';
|
||||||
import '../res/default.dart';
|
import 'package:server_box/data/res/default.dart';
|
||||||
|
|
||||||
class SettingStore extends PersistentStore {
|
class SettingStore extends PersistentStore {
|
||||||
SettingStore() : super('setting');
|
SettingStore() : super('setting');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
|
||||||
import '../model/server/snippet.dart';
|
import 'package:server_box/data/model/server/snippet.dart';
|
||||||
|
|
||||||
class SnippetStore extends PersistentStore {
|
class SnippetStore extends PersistentStore {
|
||||||
SnippetStore() : super('snippet');
|
SnippetStore() : super('snippet');
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
"goBackQ": "Zurückkommen?",
|
"goBackQ": "Zurückkommen?",
|
||||||
"goto": "Pfad öffnen",
|
"goto": "Pfad öffnen",
|
||||||
"hideTitleBar": "Titelleiste ausblenden",
|
"hideTitleBar": "Titelleiste ausblenden",
|
||||||
"hideTitleBarTip": "Nach dem Einschalten halten Sie bitte die drei Tasten in der oberen rechten Ecke gedrückt, um sie zu ziehen.",
|
|
||||||
"highlight": "Code highlight",
|
"highlight": "Code highlight",
|
||||||
"homeWidgetUrlConfig": "Home-Widget-Link konfigurieren",
|
"homeWidgetUrlConfig": "Home-Widget-Link konfigurieren",
|
||||||
"host": "Host",
|
"host": "Host",
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
"goBackQ": "Go back?",
|
"goBackQ": "Go back?",
|
||||||
"goto": "Go to",
|
"goto": "Go to",
|
||||||
"hideTitleBar": "Hide title bar",
|
"hideTitleBar": "Hide title bar",
|
||||||
"hideTitleBarTip": "After turning it on, please hold down the three buttons in the top right corner to drag.",
|
|
||||||
"highlight": "Code highlighting",
|
"highlight": "Code highlighting",
|
||||||
"homeWidgetUrlConfig": "Config home widget url",
|
"homeWidgetUrlConfig": "Config home widget url",
|
||||||
"host": "Host",
|
"host": "Host",
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
"goBackQ": "¿Regresar?",
|
"goBackQ": "¿Regresar?",
|
||||||
"goto": "Ir a",
|
"goto": "Ir a",
|
||||||
"hideTitleBar": "Ocultar barra de título",
|
"hideTitleBar": "Ocultar barra de título",
|
||||||
"hideTitleBarTip": "Después de encenderlo, mantenga presionados los tres botones en la esquina superior derecha para arrastrar.",
|
|
||||||
"highlight": "Resaltar código",
|
"highlight": "Resaltar código",
|
||||||
"homeWidgetUrlConfig": "Configuración de URL del widget de inicio",
|
"homeWidgetUrlConfig": "Configuración de URL del widget de inicio",
|
||||||
"host": "Anfitrión",
|
"host": "Anfitrión",
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
"goBackQ": "Revenir en arrière ?",
|
"goBackQ": "Revenir en arrière ?",
|
||||||
"goto": "Aller à",
|
"goto": "Aller à",
|
||||||
"hideTitleBar": "Masquer la barre de titre",
|
"hideTitleBar": "Masquer la barre de titre",
|
||||||
"hideTitleBarTip": "Après l'avoir allumé, veuillez maintenir les trois boutons dans le coin supérieur droit pour les faire glisser.",
|
|
||||||
"highlight": "Mise en surbrillance du code",
|
"highlight": "Mise en surbrillance du code",
|
||||||
"homeWidgetUrlConfig": "Configurer l'URL du widget d'accueil",
|
"homeWidgetUrlConfig": "Configurer l'URL du widget d'accueil",
|
||||||
"host": "Hôte",
|
"host": "Hôte",
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
"goBackQ": "Datang kembali?",
|
"goBackQ": "Datang kembali?",
|
||||||
"goto": "Pergi ke",
|
"goto": "Pergi ke",
|
||||||
"hideTitleBar": "Sembunyikan bilah judul",
|
"hideTitleBar": "Sembunyikan bilah judul",
|
||||||
"hideTitleBarTip": "Setelah dinyalakan, tekan dan tahan tiga tombol di sudut kanan atas untuk menyeret.",
|
|
||||||
"highlight": "Sorotan kode",
|
"highlight": "Sorotan kode",
|
||||||
"homeWidgetUrlConfig": "Konfigurasi URL Widget Rumah",
|
"homeWidgetUrlConfig": "Konfigurasi URL Widget Rumah",
|
||||||
"host": "Host",
|
"host": "Host",
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
"goBackQ": "戻りますか?",
|
"goBackQ": "戻りますか?",
|
||||||
"goto": "移動",
|
"goto": "移動",
|
||||||
"hideTitleBar": "タイトルバーを非表示にする",
|
"hideTitleBar": "タイトルバーを非表示にする",
|
||||||
"hideTitleBarTip": "電源を入れた後、右上隅の3つのボタンを押し続けてドラッグしてください。",
|
|
||||||
"highlight": "コードハイライト",
|
"highlight": "コードハイライト",
|
||||||
"homeWidgetUrlConfig": "ホームウィジェットURL設定",
|
"homeWidgetUrlConfig": "ホームウィジェットURL設定",
|
||||||
"host": "ホスト",
|
"host": "ホスト",
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
"goBackQ": "Terug gaan?",
|
"goBackQ": "Terug gaan?",
|
||||||
"goto": "Ga naar",
|
"goto": "Ga naar",
|
||||||
"hideTitleBar": "Titelbalk verbergen",
|
"hideTitleBar": "Titelbalk verbergen",
|
||||||
"hideTitleBarTip": "Houd na het inschakelen de drie knoppen in de rechterbovenhoek ingedrukt om te slepen.",
|
|
||||||
"highlight": "Code-highlight",
|
"highlight": "Code-highlight",
|
||||||
"homeWidgetUrlConfig": "Home-widget-url configureren",
|
"homeWidgetUrlConfig": "Home-widget-url configureren",
|
||||||
"host": "Host",
|
"host": "Host",
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
"goBackQ": "Voltar?",
|
"goBackQ": "Voltar?",
|
||||||
"goto": "Ir para",
|
"goto": "Ir para",
|
||||||
"hideTitleBar": "Ocultar barra de título",
|
"hideTitleBar": "Ocultar barra de título",
|
||||||
"hideTitleBarTip": "Após ligar, segure os três botões no canto superior direito para arrastar.",
|
|
||||||
"highlight": "Destaque de código",
|
"highlight": "Destaque de código",
|
||||||
"homeWidgetUrlConfig": "Configuração de URL do widget da tela inicial",
|
"homeWidgetUrlConfig": "Configuração de URL do widget da tela inicial",
|
||||||
"host": "Host",
|
"host": "Host",
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
"goBackQ": "Вернуться?",
|
"goBackQ": "Вернуться?",
|
||||||
"goto": "перейти к",
|
"goto": "перейти к",
|
||||||
"hideTitleBar": "Скрыть заголовок",
|
"hideTitleBar": "Скрыть заголовок",
|
||||||
"hideTitleBarTip": "После включения удерживайте три кнопки в правом верхнем углу, чтобы перетаскивать.",
|
|
||||||
"highlight": "подсветка кода",
|
"highlight": "подсветка кода",
|
||||||
"homeWidgetUrlConfig": "конфигурация URL виджета домашнего экрана",
|
"homeWidgetUrlConfig": "конфигурация URL виджета домашнего экрана",
|
||||||
"host": "хост",
|
"host": "хост",
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
"goBackQ": "Geri dön?",
|
"goBackQ": "Geri dön?",
|
||||||
"goto": "Git",
|
"goto": "Git",
|
||||||
"hideTitleBar": "Başlık çubuğunu gizle",
|
"hideTitleBar": "Başlık çubuğunu gizle",
|
||||||
"hideTitleBarTip": "Açtıktan sonra, sağ üst köşedeki üç düğmeyi basılı tutarak sürükleyin.",
|
|
||||||
"highlight": "Kod vurgulama",
|
"highlight": "Kod vurgulama",
|
||||||
"homeWidgetUrlConfig": "Ana sayfa widget URL'sini yapılandır",
|
"homeWidgetUrlConfig": "Ana sayfa widget URL'sini yapılandır",
|
||||||
"host": "Sunucu",
|
"host": "Sunucu",
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
"goBackQ": "返回?",
|
"goBackQ": "返回?",
|
||||||
"goto": "前往",
|
"goto": "前往",
|
||||||
"hideTitleBar": "隐藏标题栏",
|
"hideTitleBar": "隐藏标题栏",
|
||||||
"hideTitleBarTip": "开启后请按住右上角三个按钮来拖动",
|
|
||||||
"highlight": "代码高亮",
|
"highlight": "代码高亮",
|
||||||
"homeWidgetUrlConfig": "桌面部件链接配置",
|
"homeWidgetUrlConfig": "桌面部件链接配置",
|
||||||
"host": "主机",
|
"host": "主机",
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
"goBackQ": "返回?",
|
"goBackQ": "返回?",
|
||||||
"goto": "前往",
|
"goto": "前往",
|
||||||
"hideTitleBar": "隱藏標題欄",
|
"hideTitleBar": "隱藏標題欄",
|
||||||
"hideTitleBarTip": "開啟後請按住右上角三個按鈕來拖動",
|
|
||||||
"highlight": "代碼高亮",
|
"highlight": "代碼高亮",
|
||||||
"homeWidgetUrlConfig": "桌面部件鏈接配置",
|
"homeWidgetUrlConfig": "桌面部件鏈接配置",
|
||||||
"host": "主機",
|
"host": "主機",
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ class BackupPage extends StatelessWidget {
|
|||||||
|
|
||||||
Future<void> _onTapWebdavUp(BuildContext context) async {
|
Future<void> _onTapWebdavUp(BuildContext context) async {
|
||||||
webdavLoading.value = true;
|
webdavLoading.value = true;
|
||||||
final date = DateTime.now().ymdhms(ymdSep: "-", hmsSep: "-", sep: "-");
|
final date = DateTime.now().ymdhms(ymdSep: '-', hmsSep: '-', sep: '-');
|
||||||
final bakName = '$date-${Miscs.bakFileName}';
|
final bakName = '$date-${Miscs.bakFileName}';
|
||||||
try {
|
try {
|
||||||
await Backup.backup(bakName);
|
await Backup.backup(bakName);
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import 'package:server_box/data/model/container/image.dart';
|
|||||||
import 'package:server_box/data/model/container/type.dart';
|
import 'package:server_box/data/model/container/type.dart';
|
||||||
import 'package:server_box/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
|
||||||
import '../../data/model/container/ps.dart';
|
import 'package:server_box/data/model/container/ps.dart';
|
||||||
import '../../data/model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
import '../../data/provider/container.dart';
|
import 'package:server_box/data/provider/container.dart';
|
||||||
import '../widget/two_line_text.dart';
|
import 'package:server_box/view/widget/two_line_text.dart';
|
||||||
|
|
||||||
class ContainerPage extends StatefulWidget {
|
class ContainerPage extends StatefulWidget {
|
||||||
final ServerPrivateInfo spi;
|
final ServerPrivateInfo spi;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import 'package:server_box/core/extension/context/locale.dart';
|
|||||||
import 'package:server_box/data/res/highlight.dart';
|
import 'package:server_box/data/res/highlight.dart';
|
||||||
import 'package:server_box/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
|
||||||
import '../widget/two_line_text.dart';
|
import 'package:server_box/view/widget/two_line_text.dart';
|
||||||
|
|
||||||
class EditorPage extends StatefulWidget {
|
class EditorPage extends StatefulWidget {
|
||||||
/// If path is not null, then it's a file editor
|
/// If path is not null, then it's a file editor
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:server_box/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
import 'package:server_box/data/res/provider.dart';
|
import 'package:server_box/data/res/provider.dart';
|
||||||
|
|
||||||
import '../../data/model/server/ping_result.dart';
|
import 'package:server_box/data/model/server/ping_result.dart';
|
||||||
|
|
||||||
/// Only permit ipv4 / ipv6 / domain chars
|
/// Only permit ipv4 / ipv6 / domain chars
|
||||||
final targetReg = RegExp(r'[a-zA-Z0-9\.-_:]+');
|
final targetReg = RegExp(r'[a-zA-Z0-9\.-_:]+');
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import 'package:server_box/core/extension/context/locale.dart';
|
|||||||
import 'package:server_box/data/res/misc.dart';
|
import 'package:server_box/data/res/misc.dart';
|
||||||
import 'package:server_box/data/res/provider.dart';
|
import 'package:server_box/data/res/provider.dart';
|
||||||
|
|
||||||
import '../../../core/utils/server.dart';
|
import 'package:server_box/core/utils/server.dart';
|
||||||
import '../../../data/model/server/private_key_info.dart';
|
import 'package:server_box/data/model/server/private_key_info.dart';
|
||||||
|
|
||||||
const _format = 'text/plain';
|
const _format = 'text/plain';
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _standardizeLineSeparators(String value) {
|
String _standardizeLineSeparators(String value) {
|
||||||
return value.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
return value.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFAB() {
|
Widget _buildFAB() {
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:server_box/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
import 'package:server_box/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
|
||||||
import '../../../core/route.dart';
|
import 'package:server_box/core/route.dart';
|
||||||
import '../../../data/model/server/private_key_info.dart';
|
import 'package:server_box/data/model/server/private_key_info.dart';
|
||||||
import '../../../data/provider/private_key.dart';
|
import 'package:server_box/data/provider/private_key.dart';
|
||||||
|
|
||||||
class PrivateKeysListPage extends StatefulWidget {
|
class PrivateKeysListPage extends StatefulWidget {
|
||||||
const PrivateKeysListPage({super.key});
|
const PrivateKeysListPage({super.key});
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:server_box/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
import 'package:server_box/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
|
||||||
import '../../data/model/app/shell_func.dart';
|
import 'package:server_box/data/model/app/shell_func.dart';
|
||||||
import '../../data/model/server/proc.dart';
|
import 'package:server_box/data/model/server/proc.dart';
|
||||||
import '../../data/model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
import '../widget/two_line_text.dart';
|
import 'package:server_box/view/widget/two_line_text.dart';
|
||||||
|
|
||||||
class ProcessPage extends StatefulWidget {
|
class ProcessPage extends StatefulWidget {
|
||||||
final ServerPrivateInfo spi;
|
final ServerPrivateInfo spi;
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ import 'package:server_box/data/model/server/system.dart';
|
|||||||
import 'package:server_box/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
import 'package:server_box/view/widget/server_func_btns.dart';
|
import 'package:server_box/view/widget/server_func_btns.dart';
|
||||||
|
|
||||||
import '../../../../core/route.dart';
|
import 'package:server_box/core/route.dart';
|
||||||
import '../../../../data/model/server/server.dart';
|
import 'package:server_box/data/model/server/server.dart';
|
||||||
import '../../../../data/provider/server.dart';
|
import 'package:server_box/data/provider/server.dart';
|
||||||
|
|
||||||
part 'misc.dart';
|
part 'misc.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import 'package:server_box/data/model/server/custom.dart';
|
|||||||
import 'package:server_box/data/model/server/wol_cfg.dart';
|
import 'package:server_box/data/model/server/wol_cfg.dart';
|
||||||
import 'package:server_box/data/res/provider.dart';
|
import 'package:server_box/data/res/provider.dart';
|
||||||
|
|
||||||
import '../../../core/route.dart';
|
import 'package:server_box/core/route.dart';
|
||||||
import '../../../data/model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
import '../../../data/provider/private_key.dart';
|
import 'package:server_box/data/provider/private_key.dart';
|
||||||
|
|
||||||
class ServerEditPage extends StatefulWidget {
|
class ServerEditPage extends StatefulWidget {
|
||||||
const ServerEditPage({super.key, this.spi});
|
const ServerEditPage({super.key, this.spi});
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ import 'package:server_box/data/res/provider.dart';
|
|||||||
import 'package:server_box/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
import 'package:server_box/view/widget/percent_circle.dart';
|
import 'package:server_box/view/widget/percent_circle.dart';
|
||||||
|
|
||||||
import '../../../core/route.dart';
|
import 'package:server_box/core/route.dart';
|
||||||
import '../../../data/model/app/net_view.dart';
|
import 'package:server_box/data/model/app/net_view.dart';
|
||||||
import '../../../data/model/server/server.dart';
|
import 'package:server_box/data/model/server/server.dart';
|
||||||
import '../../../data/model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
import '../../../data/provider/server.dart';
|
import 'package:server_box/data/provider/server.dart';
|
||||||
import '../../widget/server_func_btns.dart';
|
import 'package:server_box/view/widget/server_func_btns.dart';
|
||||||
|
|
||||||
class ServerPage extends StatefulWidget {
|
class ServerPage extends StatefulWidget {
|
||||||
const ServerPage({super.key});
|
const ServerPage({super.key});
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import 'package:server_box/data/res/rebuild.dart';
|
|||||||
import 'package:server_box/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
import 'package:server_box/data/res/url.dart';
|
import 'package:server_box/data/res/url.dart';
|
||||||
|
|
||||||
import '../../../core/route.dart';
|
import 'package:server_box/core/route.dart';
|
||||||
import '../../../data/model/app/net_view.dart';
|
import 'package:server_box/data/model/app/net_view.dart';
|
||||||
import '../../../data/res/build_data.dart';
|
import 'package:server_box/data/res/build_data.dart';
|
||||||
|
|
||||||
const _kIconSize = 23.0;
|
const _kIconSize = 23.0;
|
||||||
|
|
||||||
@@ -1032,7 +1032,6 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
Widget _buildHideTitleBar() {
|
Widget _buildHideTitleBar() {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(l10n.hideTitleBar),
|
title: Text(l10n.hideTitleBar),
|
||||||
subtitle: Text(l10n.hideTitleBarTip, style: UIs.textGrey),
|
|
||||||
trailing: StoreSwitch(prop: _setting.hideTitleBar),
|
trailing: StoreSwitch(prop: _setting.hideTitleBar),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:server_box/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
|
||||||
import '../../../data/model/server/snippet.dart';
|
import 'package:server_box/data/model/server/snippet.dart';
|
||||||
import '/core/route.dart';
|
import 'package:server_box/core/route.dart';
|
||||||
import '/data/provider/snippet.dart';
|
import 'package:server_box/data/provider/snippet.dart';
|
||||||
|
|
||||||
class SnippetListPage extends StatefulWidget {
|
class SnippetListPage extends StatefulWidget {
|
||||||
const SnippetListPage({super.key});
|
const SnippetListPage({super.key});
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ import 'package:wakelock_plus/wakelock_plus.dart';
|
|||||||
import 'package:xterm/core.dart';
|
import 'package:xterm/core.dart';
|
||||||
import 'package:xterm/ui.dart' hide TerminalThemes;
|
import 'package:xterm/ui.dart' hide TerminalThemes;
|
||||||
|
|
||||||
import '../../../core/route.dart';
|
import 'package:server_box/core/route.dart';
|
||||||
import '../../../data/model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
import '../../../data/model/ssh/virtual_key.dart';
|
import 'package:server_box/data/model/ssh/virtual_key.dart';
|
||||||
import '../../../data/res/terminal.dart';
|
import 'package:server_box/data/res/terminal.dart';
|
||||||
|
|
||||||
const _echoPWD = 'echo \$PWD';
|
const _echoPWD = 'echo \$PWD';
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import 'package:server_box/data/res/misc.dart';
|
|||||||
import 'package:server_box/data/res/provider.dart';
|
import 'package:server_box/data/res/provider.dart';
|
||||||
import 'package:server_box/view/widget/omit_start_text.dart';
|
import 'package:server_box/view/widget/omit_start_text.dart';
|
||||||
|
|
||||||
import '../../../core/route.dart';
|
import 'package:server_box/core/route.dart';
|
||||||
import '../../../data/model/app/path_with_prefix.dart';
|
import 'package:server_box/data/model/app/path_with_prefix.dart';
|
||||||
|
|
||||||
class LocalStoragePage extends StatefulWidget {
|
class LocalStoragePage extends StatefulWidget {
|
||||||
final bool isPickFile;
|
final bool isPickFile;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import 'package:server_box/core/extension/sftpfile.dart';
|
|||||||
import 'package:server_box/core/route.dart';
|
import 'package:server_box/core/route.dart';
|
||||||
import 'package:server_box/core/utils/comparator.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/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/browser_status.dart';
|
||||||
import 'package:server_box/data/model/sftp/worker.dart';
|
import 'package:server_box/data/model/sftp/worker.dart';
|
||||||
import 'package:server_box/data/res/misc.dart';
|
import 'package:server_box/data/res/misc.dart';
|
||||||
@@ -38,111 +37,99 @@ class SftpPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||||
final _status = SftpBrowserStatus();
|
late final _status = SftpBrowserStatus(_client);
|
||||||
late final _client = widget.spi.server?.client;
|
late final _client = widget.spi.server!.client!;
|
||||||
|
final _sortOption = _SortOption().vn;
|
||||||
final _sortOption =
|
|
||||||
ValueNotifier(_SortOption(sortBy: _SortType.name, reversed: false));
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final children = [
|
||||||
|
Btn.icon(
|
||||||
|
icon: const Icon(Icons.downloading),
|
||||||
|
onTap: () => AppRoutes.sftpMission().go(context),
|
||||||
|
),
|
||||||
|
_buildSortMenu(),
|
||||||
|
_buildSearchBtn(),
|
||||||
|
];
|
||||||
|
if (isDesktop) children.add(_buildRefreshBtn());
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: CustomAppBar(
|
appBar: CustomAppBar(
|
||||||
leading: IconButton(
|
|
||||||
icon: const BackButtonIcon(),
|
|
||||||
onPressed: () {
|
|
||||||
_status.path?.update('/');
|
|
||||||
context.pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
title: TwoLineText(up: 'SFTP', down: widget.spi.name),
|
title: TwoLineText(up: 'SFTP', down: widget.spi.name),
|
||||||
actions: [
|
actions: children,
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.downloading),
|
|
||||||
onPressed: () => AppRoutes.sftpMission().go(context),
|
|
||||||
),
|
|
||||||
ValBuilder(
|
|
||||||
listenable: _sortOption,
|
|
||||||
builder: (value) {
|
|
||||||
return PopupMenuButton<_SortType>(
|
|
||||||
icon: const Icon(Icons.sort),
|
|
||||||
itemBuilder: (context) {
|
|
||||||
final currentSelectedOption = _sortOption.value;
|
|
||||||
final options = [
|
|
||||||
(_SortType.name, libL10n.name),
|
|
||||||
(_SortType.size, l10n.size),
|
|
||||||
(_SortType.time, l10n.time),
|
|
||||||
];
|
|
||||||
return options.map((r) {
|
|
||||||
final (type, name) = r;
|
|
||||||
return PopupMenuItem(
|
|
||||||
value: type,
|
|
||||||
child: Text(
|
|
||||||
type == currentSelectedOption.sortBy
|
|
||||||
? "$name (${currentSelectedOption.reversed ? '-' : '+'})"
|
|
||||||
: name,
|
|
||||||
style: TextStyle(
|
|
||||||
color: type == currentSelectedOption.sortBy
|
|
||||||
? UIs.primaryColor
|
|
||||||
: null,
|
|
||||||
fontWeight: type == currentSelectedOption.sortBy
|
|
||||||
? FontWeight.bold
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
},
|
|
||||||
onSelected: (sortBy) {
|
|
||||||
final oldValue = _sortOption.value;
|
|
||||||
if (oldValue.sortBy == sortBy) {
|
|
||||||
_sortOption.value = _SortOption(
|
|
||||||
sortBy: sortBy, reversed: !oldValue.reversed);
|
|
||||||
} else {
|
|
||||||
_sortOption.value =
|
|
||||||
_SortOption(sortBy: sortBy, reversed: false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
body: _buildFileView(),
|
body: _buildFileView(),
|
||||||
bottomNavigationBar: _buildBottom(),
|
bottomNavigationBar: _buildBottom(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildSortMenu() {
|
||||||
|
return ValBuilder(
|
||||||
|
listenable: _sortOption,
|
||||||
|
builder: (value) {
|
||||||
|
return PopupMenuButton<_SortType>(
|
||||||
|
icon: const Icon(Icons.sort),
|
||||||
|
itemBuilder: (context) {
|
||||||
|
final currentSelectedOption = _sortOption.value;
|
||||||
|
final options = [
|
||||||
|
(_SortType.name, libL10n.name),
|
||||||
|
(_SortType.size, l10n.size),
|
||||||
|
(_SortType.time, l10n.time),
|
||||||
|
];
|
||||||
|
return options.map((r) {
|
||||||
|
final (type, name) = r;
|
||||||
|
final selected = type == currentSelectedOption.sortBy;
|
||||||
|
final title = selected
|
||||||
|
? "$name (${currentSelectedOption.reversed ? '-' : '+'})"
|
||||||
|
: name;
|
||||||
|
return PopupMenuItem(
|
||||||
|
value: type,
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: selected ? UIs.primaryColor : null,
|
||||||
|
fontWeight: selected ? FontWeight.bold : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
onSelected: (sortBy) {
|
||||||
|
final old = _sortOption.value;
|
||||||
|
if (old.sortBy == sortBy) {
|
||||||
|
_sortOption.value = old.copyWith(reversed: !old.reversed);
|
||||||
|
} else {
|
||||||
|
_sortOption.value = old.copyWith(sortBy: sortBy);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildBottom() {
|
Widget _buildBottom() {
|
||||||
final children = widget.isSelect
|
final children = widget.isSelect
|
||||||
? [
|
? [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => context.pop(_status.path?.path),
|
onPressed: () => context.pop(_status.path.path),
|
||||||
icon: const Icon(Icons.done),
|
icon: const Icon(Icons.done),
|
||||||
),
|
),
|
||||||
_buildSearchBtn(),
|
_buildSearchBtn(),
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
IconButton(
|
_buildBackBtn(),
|
||||||
padding: const EdgeInsets.all(0),
|
_buildHomeBtn(),
|
||||||
onPressed: () async {
|
|
||||||
await _backward();
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
),
|
|
||||||
_buildAddBtn(),
|
_buildAddBtn(),
|
||||||
_buildGotoBtn(),
|
_buildGotoBtn(),
|
||||||
_buildUploadBtn(),
|
_buildUploadBtn(),
|
||||||
_buildSearchBtn(),
|
|
||||||
];
|
];
|
||||||
if (isDesktop) children.add(_buildRefreshBtn());
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.fromLTRB(11, 7, 11, 11),
|
padding: const EdgeInsets.fromLTRB(11, 7, 11, 11),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
OmitStartText(_status.path?.path ?? '...'),
|
OmitStartText(_status.path.path),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: children,
|
children: children,
|
||||||
@@ -153,169 +140,18 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSearchBtn() {
|
|
||||||
return IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
Stream<SftpName> find(String query) async* {
|
|
||||||
final fs = _status.files;
|
|
||||||
if (fs == null) return;
|
|
||||||
for (final f in fs) {
|
|
||||||
if (f.filename.contains(query)) yield f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final search = SearchPage(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3),
|
|
||||||
future: (q) => find(q).toList(),
|
|
||||||
builder: (ctx, e) => _buildItem(e, beforeTap: () => ctx.pop()),
|
|
||||||
);
|
|
||||||
await showSearch(context: context, delegate: search);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.search),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildUploadBtn() {
|
|
||||||
return IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
final idx = await context.showRoundDialog(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.open_in_new),
|
|
||||||
title: Text(l10n.system),
|
|
||||||
onTap: () => context.pop(1),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.folder),
|
|
||||||
title: Text(l10n.inner),
|
|
||||||
onTap: () => context.pop(0),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
final path = await () async {
|
|
||||||
switch (idx) {
|
|
||||||
case 0:
|
|
||||||
return await AppRoutes.localStorage(isPickFile: true)
|
|
||||||
.go<String>(context);
|
|
||||||
case 1:
|
|
||||||
return await Pfs.pickFilePath();
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
if (path == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final remoteDir = _status.path?.path;
|
|
||||||
if (remoteDir == null) {
|
|
||||||
context.showSnackBar('remote path is null');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final fileName = path.split(Platform.pathSeparator).lastOrNull;
|
|
||||||
final remotePath = '$remoteDir/$fileName';
|
|
||||||
Loggers.app.info('SFTP upload local: $path, remote: $remotePath');
|
|
||||||
Pros.sftp.add(
|
|
||||||
SftpReq(widget.spi, remotePath, path, SftpReqType.upload),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.upload_file),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildAddBtn() {
|
|
||||||
return IconButton(
|
|
||||||
onPressed: () => context.showRoundDialog(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.folder),
|
|
||||||
title: Text(libL10n.folder),
|
|
||||||
onTap: _mkdir,
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.insert_drive_file),
|
|
||||||
title: Text(libL10n.file),
|
|
||||||
onTap: _newFile,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
icon: const Icon(Icons.add),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildGotoBtn() {
|
|
||||||
return IconButton(
|
|
||||||
padding: const EdgeInsets.all(0),
|
|
||||||
onPressed: () async {
|
|
||||||
final p = await context.showRoundDialog<String>(
|
|
||||||
title: l10n.goto,
|
|
||||||
child: Autocomplete<String>(
|
|
||||||
optionsBuilder: (val) {
|
|
||||||
if (!Stores.setting.recordHistory.fetch()) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return Stores.history.sftpGoPath.all.cast<String>().where(
|
|
||||||
(element) => element.contains(val.text),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
fieldViewBuilder: (_, controller, node, __) {
|
|
||||||
return Input(
|
|
||||||
autoFocus: true,
|
|
||||||
icon: Icons.abc,
|
|
||||||
label: libL10n.path,
|
|
||||||
node: node,
|
|
||||||
controller: controller,
|
|
||||||
suggestion: true,
|
|
||||||
onSubmitted: (value) => context.pop(value),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (p == null || p.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_status.path?.update(p);
|
|
||||||
final suc = await _listDir() ?? false;
|
|
||||||
if (suc && Stores.setting.recordHistory.fetch()) {
|
|
||||||
Stores.history.sftpGoPath.add(p);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.gps_fixed),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildRefreshBtn() {
|
|
||||||
return IconButton(
|
|
||||||
onPressed: () => _listDir(),
|
|
||||||
icon: const Icon(Icons.refresh),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFileView() {
|
Widget _buildFileView() {
|
||||||
if (_status.files == null) {
|
if (_status.files.isEmpty) return Center(child: Text(libL10n.empty));
|
||||||
return UIs.centerLoading;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_status.files!.isEmpty) {
|
|
||||||
return const Center(
|
|
||||||
child: Text('~'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
|
onRefresh: _listDir,
|
||||||
child: FadeIn(
|
child: FadeIn(
|
||||||
key: Key(widget.spi.name + _status.path!.path),
|
key: Key(widget.spi.name + _status.path.path),
|
||||||
child: ValBuilder(
|
child: ValBuilder(
|
||||||
listenable: _sortOption,
|
listenable: _sortOption,
|
||||||
builder: (sortOption) {
|
builder: (sortOption) {
|
||||||
final files = sortOption.sortBy.sort(
|
final files = sortOption.sortBy.sort(
|
||||||
_status.files!,
|
_status.files,
|
||||||
reversed: sortOption.reversed,
|
reversed: sortOption.reversed,
|
||||||
);
|
);
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
@@ -326,7 +162,6 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onRefresh: () => _listDir(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,7 +186,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
beforeTap?.call();
|
beforeTap?.call();
|
||||||
if (isDir) {
|
if (isDir) {
|
||||||
_status.path?.update(file.filename);
|
_status.path.path = file.filename;
|
||||||
_listDir();
|
_listDir();
|
||||||
} else {
|
} else {
|
||||||
_onItemPress(file, true);
|
_onItemPress(file, true);
|
||||||
@@ -402,7 +237,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
final permStr = newPerm.perm;
|
final permStr = newPerm.perm;
|
||||||
if (ok == true && permStr != perm.perm) {
|
if (ok == true && permStr != perm.perm) {
|
||||||
await context.showLoadingDialog(fn: () async {
|
await context.showLoadingDialog(fn: () async {
|
||||||
await _client!.run('chmod $permStr "${_getRemotePath(file)}"');
|
await _client.run('chmod $permStr "${_getRemotePath(file)}"');
|
||||||
await _listDir();
|
await _listDir();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -570,7 +405,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
fn: () async {
|
fn: () async {
|
||||||
final remotePath = _getRemotePath(file);
|
final remotePath = _getRemotePath(file);
|
||||||
if (useRmr) {
|
if (useRmr) {
|
||||||
await _client!.run('rm -r "$remotePath"');
|
await _client.run('rm -r "$remotePath"');
|
||||||
} else if (file.attr.isDirectory) {
|
} else if (file.attr.isDirectory) {
|
||||||
await _status.client!.rmdir(remotePath);
|
await _status.client!.rmdir(remotePath);
|
||||||
} else {
|
} else {
|
||||||
@@ -606,7 +441,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
|
|
||||||
final (suc, err) = await context.showLoadingDialog(
|
final (suc, err) = await context.showLoadingDialog(
|
||||||
fn: () async {
|
fn: () async {
|
||||||
final dir = '${_status.path!.path}/$text';
|
final dir = '${_status.path.path}/$text';
|
||||||
await _status.client!.mkdir(dir);
|
await _status.client!.mkdir(dir);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -651,8 +486,8 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
|
|
||||||
final (suc, err) = await context.showLoadingDialog(
|
final (suc, err) = await context.showLoadingDialog(
|
||||||
fn: () async {
|
fn: () async {
|
||||||
final path = '${_status.path!.path}/$text';
|
final path = '${_status.path.path}/$text';
|
||||||
await _client!.run('touch "$path"');
|
await _client.run('touch "$path"');
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -748,7 +583,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _getRemotePath(SftpName name) {
|
String _getRemotePath(SftpName name) {
|
||||||
final prePath = _status.path!.path;
|
final prePath = _status.path.path;
|
||||||
// Only support Linux as remote now, so the seperator is '/'
|
// Only support Linux as remote now, so the seperator is '/'
|
||||||
return prePath.joinPath(name.filename, seperator: '/');
|
return prePath.joinPath(name.filename, seperator: '/');
|
||||||
}
|
}
|
||||||
@@ -761,8 +596,8 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
Future<bool?> _listDir() async {
|
Future<bool?> _listDir() async {
|
||||||
final (ret, err) = await context.showLoadingDialog(
|
final (ret, err) = await context.showLoadingDialog(
|
||||||
fn: () async {
|
fn: () async {
|
||||||
_status.client ??= await _client?.sftp();
|
_status.client ??= await _client.sftp();
|
||||||
final listPath = _status.path?.path ?? '/';
|
final listPath = _status.path.path;
|
||||||
final fs = await _status.client?.listdir(listPath);
|
final fs = await _status.client?.listdir(listPath);
|
||||||
if (fs == null) {
|
if (fs == null) {
|
||||||
return false;
|
return false;
|
||||||
@@ -776,16 +611,16 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
fs.removeAt(0);
|
fs.removeAt(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Issue #96
|
if (fs.isNotEmpty &&
|
||||||
/// Due to [WillPopScope] added in this page
|
fs.firstOrNull?.filename == '..' &&
|
||||||
/// There is no need to keep '..' folder in listdir
|
_status.path.path == '/') {
|
||||||
/// So remove it
|
|
||||||
if (fs.isNotEmpty && fs.firstOrNull?.filename == '..') {
|
|
||||||
fs.removeAt(0);
|
fs.removeAt(0);
|
||||||
}
|
}
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_status.files = fs;
|
_status.files
|
||||||
|
..clear()
|
||||||
|
..addAll(fs);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Only update history when success
|
// Only update history when success
|
||||||
@@ -803,11 +638,162 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _backward() async {
|
Future<void> _backward() async {
|
||||||
if (_status.path?.undo() ?? false) {
|
if (_status.path.undo()) {
|
||||||
await _listDir();
|
await _listDir();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildBackBtn() {
|
||||||
|
return Btn.icon(
|
||||||
|
onTap: _backward,
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSearchBtn() {
|
||||||
|
return Btn.icon(
|
||||||
|
onTap: () {
|
||||||
|
Stream<SftpName> find(String query) async* {
|
||||||
|
final fs = _status.files;
|
||||||
|
for (final f in fs) {
|
||||||
|
if (f.filename.contains(query)) yield f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showSearch(
|
||||||
|
context: context,
|
||||||
|
delegate: SearchPage(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3),
|
||||||
|
future: (q) => find(q).toList(),
|
||||||
|
builder: (ctx, e) => _buildItem(e, beforeTap: () => ctx.pop()),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.search),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildUploadBtn() {
|
||||||
|
return Btn.icon(
|
||||||
|
onTap: () async {
|
||||||
|
final idx = await context.showRoundDialog(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Btn.tile(
|
||||||
|
icon: const Icon(Icons.open_in_new),
|
||||||
|
text: l10n.system,
|
||||||
|
onTap: () => context.pop(1),
|
||||||
|
),
|
||||||
|
Btn.tile(
|
||||||
|
icon: const Icon(Icons.folder),
|
||||||
|
text: l10n.inner,
|
||||||
|
onTap: () => context.pop(0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
final path = switch (idx) {
|
||||||
|
0 =>
|
||||||
|
await AppRoutes.localStorage(isPickFile: true).go<String>(context),
|
||||||
|
1 => await Pfs.pickFilePath(),
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
if (path == null) return;
|
||||||
|
|
||||||
|
final remoteDir = _status.path.path;
|
||||||
|
final fileName = path.split(Platform.pathSeparator).lastOrNull;
|
||||||
|
final remotePath = '$remoteDir/$fileName';
|
||||||
|
Loggers.app.info('SFTP upload local: $path, remote: $remotePath');
|
||||||
|
Pros.sftp.add(
|
||||||
|
SftpReq(widget.spi, remotePath, path, SftpReqType.upload),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.upload_file),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAddBtn() {
|
||||||
|
return Btn.icon(
|
||||||
|
onTap: () => context.showRoundDialog(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Btn.tile(
|
||||||
|
icon: const Icon(Icons.folder),
|
||||||
|
text: libL10n.folder,
|
||||||
|
onTap: _mkdir,
|
||||||
|
),
|
||||||
|
Btn.tile(
|
||||||
|
icon: const Icon(Icons.insert_drive_file),
|
||||||
|
text: libL10n.file,
|
||||||
|
onTap: _newFile,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildGotoBtn() {
|
||||||
|
return Btn.icon(
|
||||||
|
onTap: () async {
|
||||||
|
final p = await context.showRoundDialog<String>(
|
||||||
|
title: l10n.goto,
|
||||||
|
child: Autocomplete<String>(
|
||||||
|
optionsBuilder: (val) {
|
||||||
|
if (!Stores.setting.recordHistory.fetch()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return Stores.history.sftpGoPath.all.cast<String>().where(
|
||||||
|
(element) => element.contains(val.text),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
fieldViewBuilder: (_, controller, node, __) {
|
||||||
|
return Input(
|
||||||
|
autoFocus: true,
|
||||||
|
icon: Icons.abc,
|
||||||
|
label: libL10n.path,
|
||||||
|
node: node,
|
||||||
|
controller: controller,
|
||||||
|
suggestion: true,
|
||||||
|
onSubmitted: (value) => context.pop(value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (p == null || p.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_status.path.path = p;
|
||||||
|
final suc = await _listDir() ?? false;
|
||||||
|
if (suc && Stores.setting.recordHistory.fetch()) {
|
||||||
|
Stores.history.sftpGoPath.add(p);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.gps_fixed),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRefreshBtn() {
|
||||||
|
return Btn.icon(
|
||||||
|
onTap: _listDir,
|
||||||
|
icon: const Icon(Icons.refresh),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHomeBtn() {
|
||||||
|
return IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
final user = widget.spi.user;
|
||||||
|
_status.path.path = user != 'root' ? '/home/$user' : '/root';
|
||||||
|
_listDir();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.home),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<void> afterFirstLayout(BuildContext context) {
|
FutureOr<void> afterFirstLayout(BuildContext context) {
|
||||||
var initPath = '/';
|
var initPath = '/';
|
||||||
@@ -817,7 +803,8 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
initPath = history;
|
initPath = history;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_status.path = AbsolutePath(widget.initPath ?? initPath);
|
|
||||||
|
_status.path.path = widget.initPath ?? initPath;
|
||||||
_listDir();
|
_listDir();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -947,5 +934,15 @@ class _SortOption {
|
|||||||
final _SortType sortBy;
|
final _SortType sortBy;
|
||||||
final bool reversed;
|
final bool reversed;
|
||||||
|
|
||||||
_SortOption({required this.sortBy, required this.reversed});
|
_SortOption({this.sortBy = _SortType.name, this.reversed = false});
|
||||||
|
|
||||||
|
_SortOption copyWith({
|
||||||
|
_SortType? sortBy,
|
||||||
|
bool? reversed,
|
||||||
|
}) {
|
||||||
|
return _SortOption(
|
||||||
|
sortBy: sortBy ?? this.sortBy,
|
||||||
|
reversed: reversed ?? this.reversed,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import 'package:server_box/data/res/provider.dart';
|
|||||||
import 'package:server_box/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
import 'package:server_box/view/page/systemd.dart';
|
import 'package:server_box/view/page/systemd.dart';
|
||||||
|
|
||||||
import '../../core/route.dart';
|
import 'package:server_box/core/route.dart';
|
||||||
import '../../core/utils/server.dart';
|
import 'package:server_box/core/utils/server.dart';
|
||||||
import '../../data/model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
|
|
||||||
class ServerFuncBtnsTopRight extends StatelessWidget {
|
class ServerFuncBtnsTopRight extends StatelessWidget {
|
||||||
final ServerPrivateInfo spi;
|
final ServerPrivateInfo spi;
|
||||||
@@ -196,17 +196,17 @@ void _gotoSSH(ServerPrivateInfo spi, BuildContext context) async {
|
|||||||
await file.delete();
|
await file.delete();
|
||||||
}
|
}
|
||||||
await file.writeAsString(getPrivateKey(spi.keyId!));
|
await file.writeAsString(getPrivateKey(spi.keyId!));
|
||||||
extraArgs.addAll(["-i", path]);
|
extraArgs.addAll(['-i', path]);
|
||||||
}
|
}
|
||||||
|
|
||||||
final sshCommand = ["ssh", "${spi.user}@${spi.ip}"] + extraArgs;
|
final sshCommand = ['ssh', '${spi.user}@${spi.ip}'] + extraArgs;
|
||||||
final system = Pfs.type;
|
final system = Pfs.type;
|
||||||
switch (system) {
|
switch (system) {
|
||||||
case Pfs.windows:
|
case Pfs.windows:
|
||||||
await Process.start("cmd", ["/c", "start"] + sshCommand);
|
await Process.start('cmd', ['/c', 'start'] + sshCommand);
|
||||||
break;
|
break;
|
||||||
case Pfs.linux:
|
case Pfs.linux:
|
||||||
await Process.start("x-terminal-emulator", ["-e"] + sshCommand);
|
await Process.start('x-terminal-emulator', ['-e'] + sshCommand);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
context.showSnackBar('Mismatch system: $system');
|
context.showSnackBar('Mismatch system: $system');
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ final class UnixPermEditor extends StatefulWidget {
|
|||||||
{super.key, required this.perm, required this.onChanged});
|
{super.key, required this.perm, required this.onChanged});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_UnixPermEditorState createState() => _UnixPermEditorState();
|
State<UnixPermEditor> createState() => _UnixPermEditorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
final class _UnixPermEditorState extends State<UnixPermEditor> {
|
final class _UnixPermEditorState extends State<UnixPermEditor> {
|
||||||
|
|||||||
Reference in New Issue
Block a user