rollback: write script to /dev/shm (#474)

This commit is contained in:
lollipopkit🏳️‍⚧️
2024-07-21 17:58:14 +08:00
committed by GitHub
parent b0936c5e6e
commit 255abe8b11
25 changed files with 226 additions and 211 deletions

View File

@@ -690,7 +690,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1018;
CURRENT_PROJECT_VERSION = 1021;
DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -700,7 +700,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1018;
MARKETING_VERSION = 1.0.1021;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -826,7 +826,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1018;
CURRENT_PROJECT_VERSION = 1021;
DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -836,7 +836,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1018;
MARKETING_VERSION = 1.0.1021;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -854,7 +854,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1018;
CURRENT_PROJECT_VERSION = 1021;
DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -864,7 +864,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1018;
MARKETING_VERSION = 1.0.1021;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -885,7 +885,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1018;
CURRENT_PROJECT_VERSION = 1021;
DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
@@ -898,7 +898,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0.1018;
MARKETING_VERSION = 1.0.1021;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
@@ -924,7 +924,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1018;
CURRENT_PROJECT_VERSION = 1021;
DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
@@ -937,7 +937,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0.1018;
MARKETING_VERSION = 1.0.1021;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -960,7 +960,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1018;
CURRENT_PROJECT_VERSION = 1021;
DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
@@ -973,7 +973,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0.1018;
MARKETING_VERSION = 1.0.1021;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -996,7 +996,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1018;
CURRENT_PROJECT_VERSION = 1021;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES;
@@ -1008,7 +1008,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1018;
MARKETING_VERSION = 1.0.1021;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
@@ -1037,7 +1037,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1018;
CURRENT_PROJECT_VERSION = 1021;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES;
@@ -1049,7 +1049,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1018;
MARKETING_VERSION = 1.0.1021;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
PRODUCT_NAME = ServerBox;
@@ -1075,7 +1075,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1018;
CURRENT_PROJECT_VERSION = 1021;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES;
@@ -1087,7 +1087,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1018;
MARKETING_VERSION = 1.0.1021;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
PRODUCT_NAME = ServerBox;

View File

@@ -19,43 +19,24 @@ enum ShellFunc {
/// srvboxm -> ServerBox Mobile
static const scriptFile = 'srvboxm_v${BuildData.script}.sh';
static const scriptPathShm = '/dev/shm/$scriptFile';
static const scriptPathHome = '~/.config/server_box/$scriptFile';
static const scriptDir = '~/.config/server_box';
static const scriptPath = '$scriptDir/$scriptFile';
static final _scriptPathMap = <String, String>{};
static String getScriptPath(String id) {
return _scriptPathMap.putIfAbsent(id, () => scriptPathShm);
}
static String setScriptPath(String id, String path) {
return _scriptPathMap[id] = path;
}
static String installShellCmd(String id) {
final path = getScriptPath(id);
return """
cat > $path
chmod 744 $path
static const String installShellCmd = """
cat > $scriptPath
chmod 744 $scriptPath
""";
}
String get flag {
switch (this) {
case ShellFunc.status:
return 's';
// case ShellFunc.docker:
// return 'd';
case ShellFunc.process:
return 'p';
case ShellFunc.shutdown:
return 'sd';
case ShellFunc.reboot:
return 'r';
case ShellFunc.suspend:
return 'sp';
}
}
String get flag => switch (this) {
ShellFunc.process => 'p',
ShellFunc.shutdown => 'sd',
ShellFunc.reboot => 'r',
ShellFunc.suspend => 'sp',
ShellFunc.status => 's',
// ShellFunc.docker=> 'd',
};
String exec(String id) => 'sh ${getScriptPath(id)} -$flag';
String get exec => 'sh $scriptPath -$flag';
String get name {
switch (this) {

View File

@@ -315,23 +315,14 @@ class ServerProvider extends ChangeNotifier {
final scriptRaw = ShellFunc.allScript(spi.custom?.cmds).uint8List;
Future<void> fn(String scriptPath) async {
try {
await s.client?.runForOutput(
scriptPath,
ShellFunc.installShellCmd,
action: (session) async {
session.stdin.add(scriptRaw);
session.stdin.close();
},
);
ShellFunc.setScriptPath(spi.id, scriptPath);
}
try {
try {
await fn(ShellFunc.scriptPathShm);
} catch (_) {
await fn(ShellFunc.scriptPathHome);
}
} on SSHAuthAbortError catch (e) {
TryLimiter.inc(sid);
s.status.err = SSHErr(type: SSHErrType.auth, message: e.toString());
@@ -343,10 +334,11 @@ class ServerProvider extends ChangeNotifier {
_setServerState(s, ServerConn.failed);
return;
} catch (e) {
final err = e.toString();
TryLimiter.inc(sid);
s.status.err = SSHErr(type: SSHErrType.auth, message: e.toString());
s.status.err = SSHErr(type: SSHErrType.writeScript, message: err);
_setServerState(s, ServerConn.failed);
Loggers.app.warning('Write script to ${spi.name} by shell', e);
Loggers.app.warning('Write script to ${spi.name} by shell', err);
}
}
@@ -363,7 +355,7 @@ class ServerProvider extends ChangeNotifier {
String? raw;
try {
raw = await s.client?.run(ShellFunc.status.exec(spi.id)).string;
raw = await s.client?.run(ShellFunc.status.exec).string;
segments = raw?.split(ShellFunc.seperator).map((e) => e.trim()).toList();
if (raw == null || raw.isEmpty || segments == null || segments.isEmpty) {
if (Stores.setting.keepStatusWhenErr.fetch()) {

View File

@@ -2,6 +2,6 @@
class BuildData {
static const String name = "ServerBox";
static const int build = 1018;
static const int script = 51;
static const int build = 1021;
static const int script = 53;
}

View File

@@ -340,5 +340,6 @@
"willTakEeffectImmediately": "Wird sofort angewendet",
"wolTip": "Nach der Konfiguration von WOL (Wake-on-LAN) wird jedes Mal, wenn der Server verbunden wird, eine WOL-Anfrage gesendet.",
"write": "Schreiben",
"writeScriptFailTip": "Das Schreiben des Skripts ist fehlgeschlagen, möglicherweise aufgrund fehlender Berechtigungen oder das Verzeichnis existiert nicht."
"writeScriptFailTip": "Das Schreiben des Skripts ist fehlgeschlagen, möglicherweise aufgrund fehlender Berechtigungen oder das Verzeichnis existiert nicht.",
"writeScriptTip": "Nach der Verbindung mit dem Server wird ein Skript in ~/.config/server_box geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen."
}

View File

@@ -340,5 +340,6 @@
"willTakEeffectImmediately": "Will take effect immediately",
"wolTip": "After configuring WOL (Wake-on-LAN), a WOL request is sent each time the server is connected.",
"write": "Write",
"writeScriptFailTip": "Writing to the script failed, possibly due to lack of permissions or the directory does not exist."
"writeScriptFailTip": "Writing to the script failed, possibly due to lack of permissions or the directory does not exist.",
"writeScriptTip": "After connecting to the server, a script will be written to ~/.config/server_box to monitor the system status. You can review the script content."
}

View File

@@ -340,5 +340,6 @@
"willTakEeffectImmediately": "Los cambios tendrán efecto inmediatamente",
"wolTip": "Después de configurar WOL (Wake-on-LAN), se envía una solicitud de WOL cada vez que se conecta el servidor.",
"write": "Escribir",
"writeScriptFailTip": "La escritura en el script falló, posiblemente por falta de permisos o porque el directorio no existe."
"writeScriptFailTip": "La escritura en el script falló, posiblemente por falta de permisos o porque el directorio no existe.",
"writeScriptTip": "Después de conectarse al servidor, se escribirá un script en ~/.config/server_box para monitorear el estado del sistema. Puedes revisar el contenido del script."
}

View File

@@ -340,5 +340,6 @@
"willTakEeffectImmediately": "Prendra effet immédiatement",
"wolTip": "Après avoir configuré le WOL (Wake-on-LAN), une requête WOL est envoyée chaque fois que le serveur est connecté.",
"write": "Écrire",
"writeScriptFailTip": "Échec de l'écriture dans le script, probablement en raison d'un manque de permissions ou que le répertoire n'existe pas."
"writeScriptFailTip": "Échec de l'écriture dans le script, probablement en raison d'un manque de permissions ou que le répertoire n'existe pas.",
"writeScriptTip": "Après la connexion au serveur, un script sera écrit dans ~/.config/server_box pour surveiller létat du système. Vous pouvez examiner le contenu du script."
}

View File

@@ -340,5 +340,6 @@
"willTakEeffectImmediately": "Akan segera berlaku",
"wolTip": "Setelah mengonfigurasi WOL (Wake-on-LAN), permintaan WOL dikirim setiap kali server terhubung.",
"write": "Tulis",
"writeScriptFailTip": "Penulisan ke skrip gagal, mungkin karena tidak ada izin atau direktori tidak ada."
"writeScriptFailTip": "Penulisan ke skrip gagal, mungkin karena tidak ada izin atau direktori tidak ada.",
"writeScriptTip": "Setelah terhubung ke server, sebuah skrip akan ditulis ke ~/.config/server_box untuk memantau status sistem. Anda dapat meninjau konten skrip tersebut."
}

View File

@@ -340,5 +340,6 @@
"willTakEeffectImmediately": "変更は即座に有効になります",
"wolTip": "WOLWake-on-LANを設定した後、サーバーに接続するたびにWOLリクエストが送信されます。",
"write": "書き込み",
"writeScriptFailTip": "スクリプトの書き込みに失敗しました。権限がないかディレクトリが存在しない可能性があります。"
"writeScriptFailTip": "スクリプトの書き込みに失敗しました。権限がないかディレクトリが存在しない可能性があります。",
"writeScriptTip": "サーバーに接続すると、システムの状態を監視するためのスクリプトが ~/.config/server_box に書き込まれます。スクリプトの内容を確認できます。"
}

View File

@@ -340,5 +340,6 @@
"willTakEeffectImmediately": "Zal onmiddellijk van kracht worden",
"wolTip": "Na het configureren van WOL (Wake-on-LAN), wordt elke keer dat de server wordt verbonden een WOL-verzoek verzonden.",
"write": "Schrijven",
"writeScriptFailTip": "Het schrijven naar het script is mislukt, mogelijk door gebrek aan rechten of omdat de map niet bestaat."
"writeScriptFailTip": "Het schrijven naar het script is mislukt, mogelijk door gebrek aan rechten of omdat de map niet bestaat.",
"writeScriptTip": "Na het verbinden met de server wordt een script geschreven naar ~/.config/server_box om de systeemstatus te monitoren. U kunt de inhoud van het script controleren."
}

View File

@@ -340,5 +340,6 @@
"willTakEeffectImmediately": "As alterações serão aplicadas imediatamente",
"wolTip": "Após configurar o WOL (Wake-on-LAN), um pedido de WOL é enviado cada vez que o servidor é conectado.",
"write": "Escrita",
"writeScriptFailTip": "Falha ao escrever no script, possivelmente devido à falta de permissões ou o diretório não existe."
"writeScriptFailTip": "Falha ao escrever no script, possivelmente devido à falta de permissões ou o diretório não existe.",
"writeScriptTip": "Após conectar ao servidor, um script será escrito em ~/.config/server_box para monitorar o status do sistema. Você pode revisar o conteúdo do script."
}

View File

@@ -340,5 +340,6 @@
"willTakEeffectImmediately": "Изменения вступят в силу немедленно",
"wolTip": "После настройки WOL (Wake-on-LAN) при каждом подключении к серверу отправляется запрос WOL.",
"write": "запись",
"writeScriptFailTip": "Запись в скрипт не удалась, возможно, из-за отсутствия прав или директории не существует."
"writeScriptFailTip": "Запись в скрипт не удалась, возможно, из-за отсутствия прав или директории не существует.",
"writeScriptTip": "После подключения к серверу скрипт будет записан в ~/.config/server_box для мониторинга состояния системы. Вы можете проверить содержимое скрипта."
}

View File

@@ -340,5 +340,6 @@
"willTakEeffectImmediately": "更改将会立即生效",
"wolTip": "在配置 WOL 后,每次连接服务器都会先发送一次 WOL 请求",
"write": "写",
"writeScriptFailTip": "写入脚本失败,可能是没有权限/目录不存在等"
"writeScriptFailTip": "写入脚本失败,可能是没有权限/目录不存在等",
"writeScriptTip": "在连接服务器后,会向 ~/.config/server_box 写入脚本来监测系统状态,你可以审查脚本内容。"
}

View File

@@ -340,5 +340,6 @@
"willTakEeffectImmediately": "更改將會立即生效",
"wolTip": "在配置 WOL網絡喚醒每次連接伺服器都會先發送一次 WOL 請求。",
"write": "写",
"writeScriptFailTip": "寫入腳本失敗,可能是沒有權限/目錄不存在等。"
"writeScriptFailTip": "寫入腳本失敗,可能是沒有權限/目錄不存在等。",
"writeScriptTip": "連接到伺服器後,將會在 ~/.config/server_box 中寫入一個腳本來監測系統狀態。你可以審查腳本內容。"
}

View File

@@ -9,7 +9,6 @@ import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:server_box/app.dart';
import 'package:server_box/core/utils/sync/icloud.dart';
import 'package:server_box/core/utils/sync/webdav.dart';
@@ -95,11 +94,7 @@ Future<void> _initData() async {
Hive.registerAdapter(ServerCustomAdapter()); // 7
Hive.registerAdapter(WakeOnLanCfgAdapter()); // 8
try {
/// Apps' data on other platforms are stored in a container that prevents
/// access by other apps. Therefore, there is no need to encrypt the data.
if (isLinux || isWindows) await SecureStore.init();
} catch (_) {}
await SharedPref.init(); // Call this before accessing any store
await Stores.setting.init();
await Stores.server.init();
@@ -127,8 +122,6 @@ void _setupDebug() {
void _doPlatformRelated() async {
if (isAndroid) {
// SharedPreferences is only used on Android for saving home widgets settings.
SharedPreferences.setPrefix('');
// try switch to highest refresh rate
FlutterDisplayMode.setHighRefreshRate();
}

View File

@@ -305,6 +305,8 @@ class BackupPage extends StatelessWidget {
final url = TextEditingController(text: Stores.setting.webdavUrl.fetch());
final user = TextEditingController(text: Stores.setting.webdavUser.fetch());
final pwd = TextEditingController(text: Stores.setting.webdavPwd.fetch());
final nodeUser = FocusNode();
final nodePwd = FocusNode();
final result = await context.showRoundDialog<bool>(
title: 'WebDAV',
child: Column(
@@ -314,14 +316,19 @@ class BackupPage extends StatelessWidget {
label: 'URL',
hint: 'https://example.com/webdav/',
controller: url,
onSubmitted: (p0) => FocusScope.of(context).requestFocus(nodeUser),
),
Input(
label: l10n.user,
controller: user,
node: nodeUser,
onSubmitted: (p0) => FocusScope.of(context).requestFocus(nodePwd),
),
Input(
label: l10n.pwd,
controller: pwd,
node: nodePwd,
onSubmitted: (_) => context.pop(true),
),
],
),

View File

@@ -118,32 +118,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
Widget _buildFAB() {
return FloatingActionButton(
tooltip: l10n.save,
onPressed: () async {
final name = _nameController.text;
final key = _standardizeLineSeparators(_keyController.text.trim());
final pwd = _pwdController.text;
if (name.isEmpty || key.isEmpty) {
context.showSnackBar(l10n.fieldMustNotEmpty);
return;
}
FocusScope.of(context).unfocus();
_loading.value = UIs.centerSizedLoading;
try {
final decrypted = await Computer.shared.start(decyptPem, [key, pwd]);
final pki = PrivateKeyInfo(id: name, key: decrypted);
if (widget.pki != null) {
Pros.key.update(widget.pki!, pki);
} else {
Pros.key.add(pki);
}
} catch (e) {
context.showSnackBar(e.toString());
rethrow;
} finally {
_loading.value = null;
}
context.pop();
},
onPressed: _onTapSave,
child: const Icon(Icons.save),
);
}
@@ -206,6 +181,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
obscureText: true,
label: l10n.pwd,
icon: Icons.password,
onSubmitted: (_) => _onTapSave(),
),
SizedBox(height: MediaQuery.of(context).size.height * 0.1),
ValBuilder(
@@ -215,4 +191,31 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
],
);
}
void _onTapSave() async {
final name = _nameController.text;
final key = _standardizeLineSeparators(_keyController.text.trim());
final pwd = _pwdController.text;
if (name.isEmpty || key.isEmpty) {
context.showSnackBar(l10n.fieldMustNotEmpty);
return;
}
FocusScope.of(context).unfocus();
_loading.value = UIs.centerSizedLoading;
try {
final decrypted = await Computer.shared.start(decyptPem, [key, pwd]);
final pki = PrivateKeyInfo(id: name, key: decrypted);
if (widget.pki != null) {
Pros.key.update(widget.pki!, pki);
} else {
Pros.key.add(pki);
}
} catch (e) {
context.showSnackBar(e.toString());
rethrow;
} finally {
_loading.value = null;
}
context.pop();
}
}

View File

@@ -52,7 +52,7 @@ class _ProcessPageState extends State<ProcessPage> {
Future<void> _refresh() async {
if (mounted) {
final result =
await _client?.run(ShellFunc.process.exec(widget.spi.id)).string;
await _client?.run(ShellFunc.process.exec).string;
if (result == null || result.isEmpty) {
context.showSnackBar(l10n.noResult);
return;

View File

@@ -174,6 +174,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
Widget _buildForm() {
final children = [
_buildWriteScriptTip(),
Input(
autoFocus: true,
controller: _nameController,
@@ -638,4 +639,19 @@ class _ServerEditPageState extends State<ServerEditPage> {
context.pop();
}
Widget _buildWriteScriptTip() {
return ListTile(
leading: const Icon(Icons.tips_and_updates).paddingOnly(left: 13),
title: Text(l10n.attention),
onTap: () {
context.showRoundDialog(
title: l10n.attention,
child: SimpleMarkdown(data: l10n.writeScriptTip),
actions: Btns.oks(onTap: () => context.pop(true)),
);
},
trailing: const Icon(Icons.keyboard_arrow_right),
).cardx;
}
}

View File

@@ -356,7 +356,7 @@ class _ServerPageState extends State<ServerPage>
Stores.setting.showSuspendTip.put(false);
}
srv.client?.execWithPwd(
ShellFunc.suspend.exec(srv.id),
ShellFunc.suspend.exec,
context: context,
id: srv.id,
);
@@ -370,7 +370,7 @@ class _ServerPageState extends State<ServerPage>
IconTextBtn(
onPressed: () => _askFor(
func: () => srv.client?.execWithPwd(
ShellFunc.shutdown.exec(srv.id),
ShellFunc.shutdown.exec,
context: context,
id: srv.id,
),
@@ -383,7 +383,7 @@ class _ServerPageState extends State<ServerPage>
IconTextBtn(
onPressed: () => _askFor(
func: () => srv.client?.execWithPwd(
ShellFunc.reboot.exec(srv.id),
ShellFunc.reboot.exec,
context: context,
id: srv.id,
),

View File

@@ -581,6 +581,35 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
void _mkdir() {
context.pop();
final textController = TextEditingController();
void onSubmitted() async {
if (textController.text.isEmpty) {
context.showRoundDialog(
child: Text(l10n.fieldMustNotEmpty),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(l10n.ok),
),
],
);
return;
}
context.pop();
try {
await context.showLoadingDialog(
fn: () async {
final dir = '${_status.path!.path}/${textController.text}';
await _status.client!.mkdir(dir);
},
onErr: (e, s) {},
);
_listDir();
} catch (e, s) {
context.showErrDialog(e: e, s: s, operation: l10n.createFolder);
}
}
context.showRoundDialog(
title: l10n.createFolder,
child: Input(
@@ -588,6 +617,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
icon: Icons.folder,
controller: textController,
label: l10n.name,
onSubmitted: (_) => onSubmitted(),
),
actions: [
TextButton(
@@ -595,33 +625,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
child: Text(l10n.cancel),
),
TextButton(
onPressed: () async {
if (textController.text.isEmpty) {
context.showRoundDialog(
child: Text(l10n.fieldMustNotEmpty),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(l10n.ok),
),
],
);
return;
}
context.pop();
try {
await context.showLoadingDialog(
fn: () async {
final dir = '${_status.path!.path}/${textController.text}';
await _status.client!.mkdir(dir);
},
onErr: (e, s) {},
);
_listDir();
} catch (e, s) {
context.showErrDialog(e: e, s: s, operation: l10n.createFolder);
}
},
onPressed: onSubmitted,
child: Text(l10n.ok, style: UIs.textRed),
),
],
@@ -631,6 +635,36 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
void _newFile() {
context.pop();
final textController = TextEditingController();
void onSubmitted() async {
if (textController.text.isEmpty) {
context.showRoundDialog(
title: l10n.attention,
child: Text(l10n.fieldMustNotEmpty),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(l10n.ok),
),
],
);
return;
}
context.pop();
try {
await context.showLoadingDialog(
fn: () async {
final path = '${_status.path!.path}/${textController.text}';
await _client!.run('touch "$path"');
},
onErr: (e, s) {},
);
_listDir();
} catch (e, s) {
context.showErrDialog(e: e, s: s, operation: l10n.createFile);
}
}
context.showRoundDialog(
title: l10n.createFile,
child: Input(
@@ -638,37 +672,11 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
icon: Icons.insert_drive_file,
controller: textController,
label: l10n.name,
onSubmitted: (_) => onSubmitted(),
),
actions: [
TextButton(
onPressed: () async {
if (textController.text.isEmpty) {
context.showRoundDialog(
title: l10n.attention,
child: Text(l10n.fieldMustNotEmpty),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(l10n.ok),
),
],
);
return;
}
context.pop();
try {
await context.showLoadingDialog(
fn: () async {
final path = '${_status.path!.path}/${textController.text}';
await _client!.run('touch "$path"');
},
onErr: (e, s) {},
);
_listDir();
} catch (e, s) {
context.showErrDialog(e: e, s: s, operation: l10n.createFile);
}
},
onPressed: onSubmitted,
child: Text(l10n.ok, style: UIs.textRed),
),
],
@@ -678,6 +686,36 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
void _rename(SftpName file) {
context.pop();
final textController = TextEditingController(text: file.filename);
void onSubmitted() async {
if (textController.text.isEmpty) {
context.showRoundDialog(
title: l10n.attention,
child: Text(l10n.fieldMustNotEmpty),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(l10n.ok),
),
],
);
return;
}
context.pop();
try {
await context.showLoadingDialog(
fn: () async {
final newName = textController.text;
await _status.client?.rename(file.filename, newName);
},
onErr: (e, s) {},
);
_listDir();
} catch (e, s) {
context.showErrDialog(e: e, s: s, operation: l10n.rename);
}
}
context.showRoundDialog(
title: l10n.rename,
child: Input(
@@ -685,38 +723,12 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
icon: Icons.abc,
controller: textController,
label: l10n.name,
onSubmitted: (_) => onSubmitted(),
),
actions: [
TextButton(onPressed: () => context.pop(), child: Text(l10n.cancel)),
TextButton(
onPressed: () async {
if (textController.text.isEmpty) {
context.showRoundDialog(
title: l10n.attention,
child: Text(l10n.fieldMustNotEmpty),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(l10n.ok),
),
],
);
return;
}
context.pop();
try {
await context.showLoadingDialog(
fn: () async {
final newName = textController.text;
await _status.client?.rename(file.filename, newName);
},
onErr: (e, s) {},
);
_listDir();
} catch (e, s) {
context.showErrDialog(e: e, s: s, operation: l10n.rename);
}
},
onPressed: onSubmitted,
child: Text(l10n.rename, style: UIs.textRed),
),
],

View File

@@ -471,7 +471,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1018;
CURRENT_PROJECT_VERSION = 1021;
DEVELOPMENT_TEAM = BA88US33G6;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Server Box";
@@ -481,7 +481,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0.1018;
MARKETING_VERSION = 1.0.1021;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "Server Box";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -608,7 +608,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1018;
CURRENT_PROJECT_VERSION = 1021;
DEVELOPMENT_TEAM = BA88US33G6;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Server Box";
@@ -618,7 +618,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0.1018;
MARKETING_VERSION = 1.0.1021;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "Server Box";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -638,7 +638,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1018;
CURRENT_PROJECT_VERSION = 1021;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = BA88US33G6;
INFOPLIST_FILE = Runner/Info.plist;
@@ -649,7 +649,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0.1018;
MARKETING_VERSION = 1.0.1021;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "Server Box";
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@@ -385,8 +385,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "v1.0.74"
resolved-ref: "42000d49fd62ee202a4b37d0a97c29f77c83d0d8"
ref: "v1.0.77"
resolved-ref: "57c4c0187673ea9c38b39889ac467be4a8bbddba"
url: "https://github.com/lppcg/fl_lib"
source: git
version: "0.0.1"

View File

@@ -1,7 +1,7 @@
name: server_box
description: server status & toolbox app.
publish_to: 'none'
version: 1.0.1018+1018
version: 1.0.1021+1021
environment:
sdk: ">=3.0.0"
@@ -61,11 +61,11 @@ dependencies:
fl_lib:
git:
url: https://github.com/lppcg/fl_lib
ref: v1.0.74
ref: v1.0.77
dependency_overrides:
# dartssh2:
# path: ../dartssh2
# dartssh2:
# path: ../dartssh2
# fl_lib:
# path: ../fl_lib
# xterm: