mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2026-02-16 05:05:39 +01:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3b48fc01c | ||
|
|
8be94aa09c | ||
|
|
5db1253ab8 | ||
|
|
ceedd86310 | ||
|
|
6a0254623f | ||
|
|
1c6ec56032 | ||
|
|
287869ed45 | ||
|
|
e4dbc3ba12 | ||
|
|
426e5689f8 | ||
|
|
afda5fd4a4 | ||
|
|
0a21b2820c | ||
|
|
87b3b76b0b | ||
|
|
41ec46f1d3 | ||
|
|
7a359588db | ||
|
|
255abe8b11 | ||
|
|
b0936c5e6e |
14
.github/FUNDING.yml
vendored
Normal file
14
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: lollipopkit
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||
custom: ['https://afdian.com/a/lollipopkit'] # Replace with up to 4 custom sponsorship URLs
|
||||
16
README.md
16
README.md
@@ -27,11 +27,13 @@ Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/rel
|
||||
- Github releases are built by Github Actions
|
||||
- Other sources are built by themselves
|
||||
|
||||
|
||||
## 🔖 Feature
|
||||
- `Status chart` (CPU, Sensors, GPU...), `SSH` Term, `SFTP`, `Docker & Pkg & Process`...
|
||||
- `Status chart` (CPU, Sensors, GPU...), `SSH` Term, `SFTP`, `Docker & Process`...
|
||||
- Platform specific: `Bio auth`、`Msg push`、`Home widget`、`watchOS App`...
|
||||
- English, 简体中文; Deutsch [@its-tom](https://github.com/its-tom), 繁體中文 [@kalashnikov](https://github.com/kalashnikov), Indonesian [@azkadev](https://github.com/azkadev), Français [@FrancXPT](https://github.com/FrancXPT), Dutch [@QazCetelic](https://github.com/QazCetelic); Español, Русский язык, Português, 日本語 (Generated by GPT)
|
||||
|
||||
|
||||
## 🏙️ ScreenShots
|
||||
<table>
|
||||
<tr>
|
||||
@@ -60,8 +62,16 @@ After you read the above, you can open an [issue](https://github.com/lollipopkit
|
||||
|
||||
|
||||
## 🧱 Contribution
|
||||
- Any positive contribution is welcome.
|
||||
- [l10n guide](https://blog.lolli.tech/faq/) can be found in my blog.
|
||||
Any positive contribution is welcome.
|
||||
|
||||
### Development
|
||||
1. Setup [Flutter](https://flutter.dev/docs/get-started/install) environment.
|
||||
2. Clone this repo, run `flutter run` to start the app.
|
||||
3. Run `dart run fl_build -p PLATFORM` to build the app.
|
||||
|
||||
### Translation
|
||||
- [Guide](https://blog.lpkt.cn/posts/faq/) can be found in my blog.
|
||||
- We need your help! Just feel free to open a PR.
|
||||
|
||||
|
||||
## 💡 My other apps
|
||||
|
||||
13
README_zh.md
13
README_zh.md
@@ -26,8 +26,9 @@ Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/rel
|
||||
- Github 的包由 Github Actions 构建
|
||||
- 其他来源由其所有者构建
|
||||
|
||||
|
||||
## 🔖 特点
|
||||
- `状态图表`(CPU、传感器、GPU 等), `SSH` 终端, `SFTP`, `Docker & 包 & 进程` 管理器...
|
||||
- `状态图表`(CPU、传感器、GPU 等), `SSH` 终端, `SFTP`, `Docker & 进程` 管理...
|
||||
- 特殊支持:`生物认证`、`推送`、`桌面小部件`、`watchOS App`、`跟随系统颜色`...
|
||||
- 本地化
|
||||
- English, 简体中文
|
||||
@@ -65,9 +66,15 @@ Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/rel
|
||||
|
||||
|
||||
## 🧱 贡献
|
||||
- 任何正面的贡献都欢迎。
|
||||
- [本地化翻译指南](https://blog.lolli.tech/faq/) 可在我的博客中找到。
|
||||
任何正面的贡献都欢迎。
|
||||
|
||||
### 开发
|
||||
1. 安装 [Flutter](https://flutter.dev/docs/get-started/install)
|
||||
2. 克隆这个仓库, 运行 `flutter run` 启动应用
|
||||
3. 运行 `dart run fl_build -p PLATFORM` 构建应用
|
||||
|
||||
### 翻译
|
||||
[指南](https://blog.lolli.tech/faq/) 可在我的博客中找到。
|
||||
|
||||
## 💡 我的其它 Apps
|
||||
- [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - 支持 OpenAI API 的 第三方全平台客户端。
|
||||
|
||||
@@ -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 = 1034;
|
||||
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.1034;
|
||||
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 = 1034;
|
||||
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.1034;
|
||||
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 = 1034;
|
||||
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.1034;
|
||||
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 = 1034;
|
||||
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.1034;
|
||||
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 = 1034;
|
||||
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.1034;
|
||||
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 = 1034;
|
||||
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.1034;
|
||||
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 = 1034;
|
||||
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.1034;
|
||||
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 = 1034;
|
||||
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.1034;
|
||||
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 = 1034;
|
||||
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.1034;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
||||
PRODUCT_NAME = ServerBox;
|
||||
|
||||
38
lib/app.dart
38
lib/app.dart
@@ -3,6 +3,7 @@ import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:fl_lib/l10n/gen_l10n/lib_l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:locale_names/locale_names.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/res/build_data.dart';
|
||||
import 'package:server_box/data/res/rebuild.dart';
|
||||
@@ -23,7 +24,18 @@ class MyApp extends StatelessWidget {
|
||||
builder: (context, _) {
|
||||
if (!Stores.setting.useSystemPrimaryColor.fetch()) {
|
||||
UIs.colorSeed = Color(Stores.setting.primaryColor.fetch());
|
||||
return _buildApp(context);
|
||||
return _buildApp(
|
||||
context,
|
||||
light: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorSchemeSeed: UIs.colorSeed,
|
||||
),
|
||||
dark: ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
colorSchemeSeed: UIs.colorSeed,
|
||||
),
|
||||
);
|
||||
}
|
||||
return DynamicColorBuilder(
|
||||
builder: (light, dark) {
|
||||
@@ -36,10 +48,10 @@ class MyApp extends StatelessWidget {
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: dark,
|
||||
);
|
||||
if (context.isDark && light != null) {
|
||||
UIs.primaryColor = light.primary;
|
||||
} else if (!context.isDark && dark != null) {
|
||||
if (context.isDark && dark != null) {
|
||||
UIs.primaryColor = dark.primary;
|
||||
} else if (!context.isDark && light != null) {
|
||||
UIs.primaryColor = light.primary;
|
||||
}
|
||||
return _buildApp(context, light: lightTheme, dark: darkTheme);
|
||||
},
|
||||
@@ -48,7 +60,8 @@ class MyApp extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildApp(BuildContext ctx, {ThemeData? light, ThemeData? dark}) {
|
||||
Widget _buildApp(BuildContext ctx,
|
||||
{required ThemeData light, required ThemeData dark}) {
|
||||
final tMode = Stores.setting.themeMode.fetch();
|
||||
// Issue #57
|
||||
final themeMode = switch (tMode) {
|
||||
@@ -58,16 +71,6 @@ class MyApp extends StatelessWidget {
|
||||
};
|
||||
final locale = Stores.setting.locale.fetch().toLocale;
|
||||
|
||||
light ??= ThemeData(
|
||||
useMaterial3: true,
|
||||
colorSchemeSeed: UIs.colorSeed,
|
||||
);
|
||||
dark ??= ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
colorSchemeSeed: UIs.colorSeed,
|
||||
);
|
||||
|
||||
return MaterialApp(
|
||||
locale: locale,
|
||||
localizationsDelegates: const [
|
||||
@@ -76,10 +79,11 @@ class MyApp extends StatelessWidget {
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
localeListResolutionCallback: LocaleUtil.resolve,
|
||||
navigatorObservers: [AppRouteObserver.instance],
|
||||
title: BuildData.name,
|
||||
themeMode: themeMode,
|
||||
theme: light,
|
||||
darkTheme: tMode < 3 ? dark : dark.toAmoled,
|
||||
theme: light.fixWindowsFont,
|
||||
darkTheme: (tMode < 3 ? dark : dark.toAmoled).fixWindowsFont,
|
||||
home: Builder(
|
||||
builder: (context) {
|
||||
context.setLibL10n();
|
||||
|
||||
@@ -250,6 +250,9 @@ class AppRoutes {
|
||||
}
|
||||
|
||||
static AppRoutes kvEditor({Key? key, required Map<String, String> data}) {
|
||||
return AppRoutes(KvEditor(key: key, data: data), 'kv_editor');
|
||||
return AppRoutes(
|
||||
KvEditor(key: key, args: KvEditorArgs(data: data)),
|
||||
'kv_editor',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ enum ServerFuncBtn {
|
||||
container,
|
||||
@HiveField(3)
|
||||
process,
|
||||
@HiveField(4)
|
||||
pkg,
|
||||
//@HiveField(4)
|
||||
//pkg,
|
||||
@HiveField(5)
|
||||
snippet,
|
||||
@HiveField(6)
|
||||
@@ -30,14 +30,14 @@ enum ServerFuncBtn {
|
||||
sftp,
|
||||
container,
|
||||
process,
|
||||
pkg,
|
||||
//pkg,
|
||||
snippet,
|
||||
].map((e) => e.index).toList();
|
||||
|
||||
IconData get icon => switch (this) {
|
||||
sftp => Icons.insert_drive_file,
|
||||
snippet => Icons.code,
|
||||
pkg => Icons.system_security_update,
|
||||
//pkg => Icons.system_security_update,
|
||||
container => FontAwesome.docker_brand,
|
||||
process => Icons.list_alt_outlined,
|
||||
terminal => Icons.terminal,
|
||||
@@ -47,7 +47,7 @@ enum ServerFuncBtn {
|
||||
String get toStr => switch (this) {
|
||||
sftp => 'SFTP',
|
||||
snippet => l10n.snippet,
|
||||
pkg => l10n.pkg,
|
||||
//pkg => l10n.pkg,
|
||||
container => l10n.container,
|
||||
process => l10n.process,
|
||||
terminal => l10n.terminal,
|
||||
|
||||
@@ -21,8 +21,6 @@ class ServerFuncBtnAdapter extends TypeAdapter<ServerFuncBtn> {
|
||||
return ServerFuncBtn.container;
|
||||
case 3:
|
||||
return ServerFuncBtn.process;
|
||||
case 4:
|
||||
return ServerFuncBtn.pkg;
|
||||
case 5:
|
||||
return ServerFuncBtn.snippet;
|
||||
case 6:
|
||||
@@ -47,9 +45,6 @@ class ServerFuncBtnAdapter extends TypeAdapter<ServerFuncBtn> {
|
||||
case ServerFuncBtn.process:
|
||||
writer.writeByte(3);
|
||||
break;
|
||||
case ServerFuncBtn.pkg:
|
||||
writer.writeByte(4);
|
||||
break;
|
||||
case ServerFuncBtn.snippet:
|
||||
writer.writeByte(5);
|
||||
break;
|
||||
|
||||
@@ -19,28 +19,23 @@ enum ShellFunc {
|
||||
|
||||
/// srvboxm -> ServerBox Mobile
|
||||
static const scriptFile = 'srvboxm_v${BuildData.script}.sh';
|
||||
static const scriptPath = '/dev/shm/$scriptFile';
|
||||
static const installShellCmd = """
|
||||
static const scriptDir = '~/.config/server_box';
|
||||
static const scriptPath = '$scriptDir/$scriptFile';
|
||||
|
||||
static const String installShellCmd = """
|
||||
mkdir -p $scriptDir
|
||||
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 get exec => 'sh $scriptPath -$flag';
|
||||
|
||||
@@ -198,6 +193,7 @@ enum StatusCmdType {
|
||||
'for f in /sys/class/power_supply/*/uevent; do cat "\$f"; echo; done'),
|
||||
nvidia._('nvidia-smi -q -x'),
|
||||
sensors._('sensors'),
|
||||
cpuBrand._('cat /proc/cpuinfo | grep "model name"'),
|
||||
;
|
||||
|
||||
final String cmd;
|
||||
@@ -216,6 +212,7 @@ enum BSDStatusCmdType {
|
||||
mem._('top -l 1 | grep PhysMem'),
|
||||
//temp,
|
||||
host._('hostname'),
|
||||
cpuBrand._('sysctl -n machdep.cpu.brand_string'),
|
||||
;
|
||||
|
||||
final String cmd;
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:server_box/data/model/server/time_seq.dart';
|
||||
import 'package:server_box/data/res/status.dart';
|
||||
|
||||
/// Capacity of the FIFO queue
|
||||
const _kCap = 30;
|
||||
|
||||
class Cpus extends TimeSeq<List<SingleCpuCore>> {
|
||||
Cpus(super.init1, super.init2);
|
||||
|
||||
final Map<String, int> brand = {};
|
||||
|
||||
@override
|
||||
void onUpdate() {
|
||||
_coresCount = now.length;
|
||||
@@ -23,10 +25,16 @@ class Cpus extends TimeSeq<List<SingleCpuCore>> {
|
||||
|
||||
double usedPercent({int coreIdx = 0}) {
|
||||
if (now.length != pre.length) return 0;
|
||||
final idleDelta = now[coreIdx].idle - pre[coreIdx].idle;
|
||||
final totalDelta = now[coreIdx].total - pre[coreIdx].total;
|
||||
final used = idleDelta / totalDelta;
|
||||
return used.isNaN ? 0 : 100 - used * 100;
|
||||
if (now.isEmpty) return 0;
|
||||
try {
|
||||
final idleDelta = now[coreIdx].idle - pre[coreIdx].idle;
|
||||
final totalDelta = now[coreIdx].total - pre[coreIdx].total;
|
||||
final used = idleDelta / totalDelta;
|
||||
return used.isNaN ? 0 : 100 - used * 100;
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('Cpus.usedPercent()', e, s);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int _coresCount = 0;
|
||||
@@ -175,6 +183,22 @@ class SingleCpuCore extends TimeSeqIface<SingleCpuCore> {
|
||||
}
|
||||
}
|
||||
|
||||
final class CpuBrand {
|
||||
static Map<String, int> parse(String raw) {
|
||||
final lines = raw.split('\n');
|
||||
// {brand: count}
|
||||
final brands = <String, int>{};
|
||||
for (var line in lines) {
|
||||
if (line.contains('model name')) {
|
||||
final model = line.split(':').last.trim();
|
||||
final count = brands[model] ?? 0;
|
||||
brands[model] = count + 1;
|
||||
}
|
||||
}
|
||||
return brands;
|
||||
}
|
||||
}
|
||||
|
||||
final _bsdCpuPercentReg = RegExp(r'(\d+\.\d+)%');
|
||||
|
||||
/// TODO: Change this implementation to parse cpu status on BSD system
|
||||
|
||||
@@ -42,6 +42,10 @@ class ServerPrivateInfo {
|
||||
@HiveField(11)
|
||||
final WakeOnLanCfg? wolCfg;
|
||||
|
||||
/// It only applies to SSH terminal.
|
||||
@HiveField(12)
|
||||
final Map<String, String>? envs;
|
||||
|
||||
final String id;
|
||||
|
||||
const ServerPrivateInfo({
|
||||
@@ -57,6 +61,7 @@ class ServerPrivateInfo {
|
||||
this.jumpId,
|
||||
this.custom,
|
||||
this.wolCfg,
|
||||
this.envs,
|
||||
}) : id = '$user@$ip:$port';
|
||||
|
||||
static ServerPrivateInfo fromJson(Map<String, dynamic> json) {
|
||||
@@ -76,6 +81,15 @@ class ServerPrivateInfo {
|
||||
final wolCfg = json["wolCfg"] == null
|
||||
? null
|
||||
: WakeOnLanCfg.fromJson(json["wolCfg"].cast<String, dynamic>());
|
||||
final envs_ = json["envs"] as Map<String, dynamic>?;
|
||||
final envs = <String, String>{};
|
||||
if (envs_ != null) {
|
||||
envs_.forEach((key, value) {
|
||||
if (value is String) {
|
||||
envs[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return ServerPrivateInfo(
|
||||
name: name,
|
||||
@@ -90,6 +104,7 @@ class ServerPrivateInfo {
|
||||
jumpId: jumpId,
|
||||
custom: custom,
|
||||
wolCfg: wolCfg,
|
||||
envs: envs.isEmpty ? null : envs,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,6 +138,9 @@ class ServerPrivateInfo {
|
||||
if (wolCfg != null) {
|
||||
data["wolCfg"] = wolCfg?.toJson();
|
||||
}
|
||||
if (envs != null) {
|
||||
data["envs"] = envs;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,13 +29,14 @@ class ServerPrivateInfoAdapter extends TypeAdapter<ServerPrivateInfo> {
|
||||
jumpId: fields[9] as String?,
|
||||
custom: fields[10] as ServerCustom?,
|
||||
wolCfg: fields[11] as WakeOnLanCfg?,
|
||||
envs: (fields[12] as Map?)?.cast<String, String>(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ServerPrivateInfo obj) {
|
||||
writer
|
||||
..writeByte(12)
|
||||
..writeByte(13)
|
||||
..writeByte(0)
|
||||
..write(obj.name)
|
||||
..writeByte(1)
|
||||
@@ -59,7 +60,9 @@ class ServerPrivateInfoAdapter extends TypeAdapter<ServerPrivateInfo> {
|
||||
..writeByte(10)
|
||||
..write(obj.custom)
|
||||
..writeByte(11)
|
||||
..write(obj.wolCfg);
|
||||
..write(obj.wolCfg)
|
||||
..writeByte(12)
|
||||
..write(obj.envs);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -71,6 +71,9 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
|
||||
try {
|
||||
final cpus = SingleCpuCore.parse(StatusCmdType.cpu.find(segments));
|
||||
req.ss.cpu.update(cpus);
|
||||
final brand = CpuBrand.parse(StatusCmdType.cpuBrand.find(segments));
|
||||
req.ss.cpu.brand.clear();
|
||||
req.ss.cpu.brand.addAll(brand);
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
// import 'dart:io';
|
||||
|
||||
import 'package:computer/computer.dart';
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
@@ -10,8 +10,8 @@ import 'package:server_box/core/utils/ssh_auth.dart';
|
||||
import 'package:server_box/data/model/app/error.dart';
|
||||
import 'package:server_box/data/model/app/shell_func.dart';
|
||||
import 'package:server_box/data/model/server/system.dart';
|
||||
import 'package:server_box/data/model/sftp/req.dart';
|
||||
import 'package:server_box/data/res/provider.dart';
|
||||
// import 'package:server_box/data/model/sftp/req.dart';
|
||||
// import 'package:server_box/data/res/provider.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
import '../../core/utils/server.dart';
|
||||
@@ -313,11 +313,10 @@ class ServerProvider extends ChangeNotifier {
|
||||
|
||||
_setServerState(s, ServerConn.connected);
|
||||
|
||||
// Write script to server
|
||||
// by ssh
|
||||
final scriptRaw = ShellFunc.allScript(spi.custom?.cmds).uint8List;
|
||||
|
||||
try {
|
||||
await s.client?.runForOutput(
|
||||
await s.client!.runForOutput(
|
||||
ShellFunc.installShellCmd,
|
||||
action: (session) async {
|
||||
session.stdin.add(scriptRaw);
|
||||
@@ -335,44 +334,11 @@ class ServerProvider extends ChangeNotifier {
|
||||
_setServerState(s, ServerConn.failed);
|
||||
return;
|
||||
} catch (e) {
|
||||
Loggers.app.warning('Write script to ${spi.name} by shell', e);
|
||||
|
||||
/// by sftp
|
||||
final localPath = Paths.doc.joinPath('install.sh');
|
||||
final file = File(localPath);
|
||||
try {
|
||||
file.writeAsBytes(scriptRaw);
|
||||
final completer = Completer();
|
||||
final homePath = (await s.client?.run('echo \$HOME').string)?.trim();
|
||||
if (homePath == null || homePath.isEmpty) {
|
||||
throw Exception('Got empty home path');
|
||||
}
|
||||
final reqId = Pros.sftp.add(
|
||||
SftpReq(
|
||||
spi,
|
||||
ShellFunc.scriptPath,
|
||||
localPath,
|
||||
SftpReqType.upload,
|
||||
),
|
||||
completer: completer,
|
||||
);
|
||||
await completer.future;
|
||||
final err = Pros.sftp.get(reqId)?.error;
|
||||
if (err != null) {
|
||||
throw err;
|
||||
}
|
||||
} catch (ee) {
|
||||
TryLimiter.inc(sid);
|
||||
s.status.err = SSHErr(
|
||||
type: SSHErrType.writeScript,
|
||||
message: '$e\n\n$ee',
|
||||
);
|
||||
_setServerState(s, ServerConn.failed);
|
||||
Loggers.app.warning('Write script to ${spi.name} by sftp', ee);
|
||||
return;
|
||||
} finally {
|
||||
if (await file.exists()) await file.delete();
|
||||
}
|
||||
final err = e.toString();
|
||||
TryLimiter.inc(sid);
|
||||
s.status.err = SSHErr(type: SSHErrType.writeScript, message: err);
|
||||
_setServerState(s, ServerConn.failed);
|
||||
Loggers.app.warning('Write script to ${spi.name} by shell', err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,26 +428,4 @@ class ServerProvider extends ChangeNotifier {
|
||||
// reset try times only after prepared successfully
|
||||
TryLimiter.reset(sid);
|
||||
}
|
||||
|
||||
// Future<SnippetResult?> runSnippet(String id, Snippet snippet) async {
|
||||
// final server = _servers[id];
|
||||
// if (server == null) return null;
|
||||
// final watch = Stopwatch()..start();
|
||||
// final result = await server.client?.run(snippet.fmtWithArgs(server.spi)).string;
|
||||
// final time = watch.elapsed;
|
||||
// watch.stop();
|
||||
// if (result == null) return null;
|
||||
// return SnippetResult(
|
||||
// dest: _servers[id]?.spi.name,
|
||||
// result: result,
|
||||
// time: time,
|
||||
// );
|
||||
// }
|
||||
|
||||
// Future<List<SnippetResult?>> runSnippetsMulti(
|
||||
// List<String> ids,
|
||||
// Snippet snippet,
|
||||
// ) async {
|
||||
// return await Future.wait(ids.map((id) async => runSnippet(id, snippet)));
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -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 = 1034;
|
||||
static const int script = 54;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ abstract final class GithubIds {
|
||||
'azkadev',
|
||||
'kalashnikov',
|
||||
'calvinweb',
|
||||
'No06',
|
||||
'QazCetelic',
|
||||
'RainSunMe',
|
||||
'FrancXPT',
|
||||
@@ -20,6 +21,7 @@ abstract final class GithubIds {
|
||||
'jaychoubaby',
|
||||
'fecture',
|
||||
'Tao173',
|
||||
'Jasonzhu1207',
|
||||
'QingAnLe',
|
||||
'wxdjs',
|
||||
'Aeorq',
|
||||
@@ -72,7 +74,6 @@ abstract final class GithubIds {
|
||||
'pgs666',
|
||||
'FHU-yezi',
|
||||
'ZRY233',
|
||||
'Jasonzhu1207',
|
||||
'sakuraanzu',
|
||||
'licaon-kter',
|
||||
'77160860',
|
||||
@@ -84,6 +85,16 @@ abstract final class GithubIds {
|
||||
'Mooling0602',
|
||||
'IllTamer',
|
||||
'marlkiller',
|
||||
'hlarc',
|
||||
'itsandrewpao',
|
||||
'StudyingLover',
|
||||
'QJAG1024',
|
||||
'Wuming-HUST',
|
||||
'WolfCanglong',
|
||||
'liwenjie119',
|
||||
'logce',
|
||||
'h-lyf',
|
||||
'88484396',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -284,6 +284,10 @@ class SettingStore extends PersistentStore {
|
||||
|
||||
late final letterCache = property('letterCache', false);
|
||||
|
||||
/// Set it to `$EDITOR`, `vim` and etc. to use remote system editor in SSH terminal.
|
||||
/// Set it empty to use local editor GUI.
|
||||
late final sftpEditor = property('sftpEditor', '');
|
||||
|
||||
// Never show these settings for users
|
||||
//
|
||||
// ------BEGIN------
|
||||
|
||||
@@ -18,13 +18,15 @@ final class _IntroPage extends StatelessWidget {
|
||||
final padTop = cons.maxHeight * .16;
|
||||
final pages_ = pages.map((e) => e(context, padTop)).toList();
|
||||
return IntroPage(
|
||||
pages: pages_,
|
||||
onDone: (ctx) {
|
||||
Stores.setting.introVer.put(BuildData.build);
|
||||
Navigator.of(ctx).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const HomePage()),
|
||||
);
|
||||
},
|
||||
args: IntroPageArgs(
|
||||
pages: pages_,
|
||||
onDone: (ctx) {
|
||||
Stores.setting.introVer.put(BuildData.build);
|
||||
Navigator.of(ctx).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const HomePage()),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -38,7 +40,7 @@ final class _IntroPage extends StatelessWidget {
|
||||
IntroPage.title(icon: BoxIcons.bxs_terminal, big: true),
|
||||
SizedBox(height: padTop),
|
||||
ListTile(
|
||||
leading: const Icon(Bootstrap.input_cursor),
|
||||
leading: const Icon(Bootstrap.alphabet),
|
||||
title: Text(l10n.letterCache),
|
||||
subtitle: Text(l10n.letterCacheTip, style: UIs.textGrey),
|
||||
trailing: StoreSwitch(prop: _setting.letterCache),
|
||||
@@ -61,8 +63,8 @@ final class _IntroPage extends StatelessWidget {
|
||||
trailing: StoreSwitch(prop: _setting.sftpRmrDir),
|
||||
).cardx,
|
||||
ListTile(
|
||||
leading: const Icon(IonIcons.stats_chart, size: _kIconSize),
|
||||
title: Text(l10n.parseContainerStats),
|
||||
leading: const Icon(MingCute.chart_line_line, size: _kIconSize),
|
||||
title: Text(l10n.stat),
|
||||
subtitle: Text(l10n.parseContainerStatsTip, style: UIs.textGrey),
|
||||
trailing: StoreSwitch(prop: _setting.containerParseStat),
|
||||
).cardx,
|
||||
@@ -90,7 +92,7 @@ final class _IntroPage extends StatelessWidget {
|
||||
final selected = await ctx.showPickSingleDialog(
|
||||
title: l10n.language,
|
||||
items: AppLocalizations.supportedLocales,
|
||||
name: (p0) => p0.code,
|
||||
name: (p0) => '${p0.nativeDisplayLanguage} (${p0.code})',
|
||||
initial: _setting.locale.fetch().toLocale,
|
||||
);
|
||||
if (selected != null) {
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
"editor": "Editor",
|
||||
"editorHighlightTip": "Die Leistung der aktuellen Codehervorhebung ist schlechter und kann zur Verbesserung optional ausgeschaltet werden.",
|
||||
"encode": "Encode",
|
||||
"envVars": "Umgebungsvariable",
|
||||
"error": "Fehler",
|
||||
"exampleName": "Servername",
|
||||
"experimentalFeature": "Experimentelles Feature",
|
||||
@@ -154,7 +155,7 @@
|
||||
"lastTry": "Letzter Versuch",
|
||||
"launchPage": "Startseite",
|
||||
"letterCache": "Buchstaben-Caching",
|
||||
"letterCacheTip": "Empfohlen zu aktivieren, aber die Aktivierung verhindert die Eingabe von CJK (Chinesisch, Japanisch, Koreanisch) Zeichen",
|
||||
"letterCacheTip": "Empfohlen, zu deaktivieren, aber nach dem Deaktivieren können keine CJK-Zeichen eingegeben werden.",
|
||||
"license": "Lizenzen",
|
||||
"light": "Hell",
|
||||
"loadingFiles": "Lädt Dateien...",
|
||||
@@ -204,7 +205,6 @@
|
||||
"open": "Öffnen",
|
||||
"openLastPath": "Öffnen Sie den letzten Pfad",
|
||||
"openLastPathTip": "Verschiedene Server haben unterschiedliche Einträge, und der Eintrag ist der Pfad zum Ausgang",
|
||||
"parseContainerStats": "Den Status der Container-Belegung analysieren",
|
||||
"parseContainerStatsTip": "Das Analysieren des Belegungsstatus durch Docker ist relativ langsam",
|
||||
"paste": "Einfügen",
|
||||
"path": "Pfad",
|
||||
@@ -264,6 +264,7 @@
|
||||
"serverTabUnkown": "Unbekannter Status",
|
||||
"setting": "Einstellungen",
|
||||
"sftpDlPrepare": "Verbindung vorbereiten...",
|
||||
"sftpEditorTip": "Wenn leer, verwenden Sie den im App integrierten Dateieditor. Wenn ein Wert vorhanden ist, wird der Editor des Remote-Servers verwendet, z.B. `vim` (es wird empfohlen, automatisch gemäß `EDITOR` zu ermitteln).",
|
||||
"sftpRmrDirSummary": "Verwenden Sie \"rm -r\", um das Verzeichnis in SFTP zu löschen.",
|
||||
"sftpSSHConnected": "SFTP Verbunden",
|
||||
"sftpShowFoldersFirst": "Ordner zuerst anzeigen",
|
||||
@@ -278,6 +279,7 @@
|
||||
"sshTip": "Diese Funktion befindet sich jetzt in der Experimentierphase.\n\nBitte melde Bugs auf {url} oder mach mit bei der Entwicklung.",
|
||||
"sshVirtualKeyAutoOff": "Automatische Umschaltung der virtuellen Tasten",
|
||||
"start": "Start",
|
||||
"stat": "Statistik",
|
||||
"stats": "Statistik",
|
||||
"stop": "Stop",
|
||||
"stopped": "Ausgelaufen",
|
||||
@@ -340,5 +342,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."
|
||||
}
|
||||
@@ -92,6 +92,7 @@
|
||||
"editor": "Editor",
|
||||
"editorHighlightTip": "The current code highlighting performance is not ideal and can be optionally turned off to improve.",
|
||||
"encode": "Encode",
|
||||
"envVars": "Environment variable",
|
||||
"error": "Error",
|
||||
"exampleName": "Example name",
|
||||
"experimentalFeature": "Experimental feature",
|
||||
@@ -154,7 +155,7 @@
|
||||
"lastTry": "Last try",
|
||||
"launchPage": "Launch page",
|
||||
"letterCache": "Letter caching",
|
||||
"letterCacheTip": "Recommended to enable, but enabling it prevents the input of CJK (Chinese, Japanese, Korean) characters",
|
||||
"letterCacheTip": "Recommended to disable, but after disabling, it will be impossible to input CJK characters.",
|
||||
"license": "License",
|
||||
"light": "Light",
|
||||
"loadingFiles": "Loading files...",
|
||||
@@ -204,7 +205,6 @@
|
||||
"open": "Open",
|
||||
"openLastPath": "Open the last path",
|
||||
"openLastPathTip": "Different servers will have different logs, and the log is the path to the exit",
|
||||
"parseContainerStats": "Parse the container occupancy status",
|
||||
"parseContainerStatsTip": "Parsing the occupancy status of Docker is relatively slow.",
|
||||
"paste": "Paste",
|
||||
"path": "Path",
|
||||
@@ -264,6 +264,7 @@
|
||||
"serverTabUnkown": "Unknown state",
|
||||
"setting": "Settings",
|
||||
"sftpDlPrepare": "Preparing to connect...",
|
||||
"sftpEditorTip": "If empty, use the built-in file editor of the app. If a value is present, use the remote server’s editor, e.g., `vim` (recommended to automatically detect according to `EDITOR`).",
|
||||
"sftpRmrDirSummary": "Use `rm -r` to delete a folder in SFTP.",
|
||||
"sftpSSHConnected": "SFTP Connected",
|
||||
"sftpShowFoldersFirst": "Display folders first",
|
||||
@@ -278,6 +279,7 @@
|
||||
"sshTip": "This function is now in the experimental stage.\n\nPlease report bugs on {url} or join our development.",
|
||||
"sshVirtualKeyAutoOff": "Auto switching of virtual keys",
|
||||
"start": "Start",
|
||||
"stat": "Statistics",
|
||||
"stats": "Statistics",
|
||||
"stop": "Stop",
|
||||
"stopped": "Stopped",
|
||||
@@ -340,5 +342,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."
|
||||
}
|
||||
@@ -92,6 +92,7 @@
|
||||
"editor": "Editor",
|
||||
"editorHighlightTip": "El rendimiento del resaltado de código es bastante pobre actualmente, puedes elegir desactivarlo para mejorar.",
|
||||
"encode": "Codificar",
|
||||
"envVars": "Variable de entorno",
|
||||
"error": "Error",
|
||||
"exampleName": "Ejemplo de nombre",
|
||||
"experimentalFeature": "Función experimental",
|
||||
@@ -154,7 +155,7 @@
|
||||
"lastTry": "Último intento",
|
||||
"launchPage": "Página de lanzamiento",
|
||||
"letterCache": "Caché de letras",
|
||||
"letterCacheTip": "Se recomienda activar, pero al activarlo se impide la introducción de caracteres CJK (chino, japonés, coreano)",
|
||||
"letterCacheTip": "Recomendado desactivar, pero después de desactivarlo, no se podrán ingresar caracteres CJK.",
|
||||
"license": "Licencia de código abierto",
|
||||
"light": "Claro",
|
||||
"loadingFiles": "Cargando directorio...",
|
||||
@@ -204,7 +205,6 @@
|
||||
"open": "Abrir",
|
||||
"openLastPath": "Abrir el último camino",
|
||||
"openLastPathTip": "Los diferentes servidores tendrán diferentes registros, y lo que se registra es la ruta de salida",
|
||||
"parseContainerStats": "Analizar estado de uso del contenedor",
|
||||
"parseContainerStatsTip": "El análisis del estado de uso de Docker es bastante lento",
|
||||
"paste": "Pegar",
|
||||
"path": "Ruta",
|
||||
@@ -264,6 +264,7 @@
|
||||
"serverTabUnkown": "Estado desconocido",
|
||||
"setting": "Configuración",
|
||||
"sftpDlPrepare": "Preparando para conectar al servidor...",
|
||||
"sftpEditorTip": "Si está vacío, use el editor de archivos incorporado de la aplicación. Si hay un valor, use el editor del servidor remoto, por ejemplo, `vim` (se recomienda detectar automáticamente según `EDITOR`).",
|
||||
"sftpRmrDirSummary": "Usar `rm -r` en SFTP para eliminar directorios",
|
||||
"sftpSSHConnected": "SFTP conectado...",
|
||||
"sftpShowFoldersFirst": "Mostrar carpetas primero",
|
||||
@@ -278,6 +279,7 @@
|
||||
"sshTip": "Esta función está en fase de pruebas.\n\nPor favor, informa los problemas en {url}, o únete a nuestro desarrollo.",
|
||||
"sshVirtualKeyAutoOff": "Desactivación automática de teclas virtuales",
|
||||
"start": "Iniciar",
|
||||
"stat": "Estadísticas",
|
||||
"stats": "Estadísticas",
|
||||
"stop": "Detener",
|
||||
"stopped": "Detenido",
|
||||
@@ -340,5 +342,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."
|
||||
}
|
||||
@@ -92,6 +92,7 @@
|
||||
"editor": "Éditeur",
|
||||
"editorHighlightTip": "La performance actuelle de mise en surbrillance du code est pire et peut être désactivée en option pour s'améliorer.",
|
||||
"encode": "Encoder",
|
||||
"envVars": "Variable d’environnement",
|
||||
"error": "Erreur",
|
||||
"exampleName": "Nom de l'exemple",
|
||||
"experimentalFeature": "Fonctionnalité expérimentale",
|
||||
@@ -154,7 +155,7 @@
|
||||
"lastTry": "Dernière tentative",
|
||||
"launchPage": "Page de lancement",
|
||||
"letterCache": "Mise en cache des lettres",
|
||||
"letterCacheTip": "Recommandé à activer, mais son activation empêche la saisie de caractères CJK (chinois, japonais, coréen)",
|
||||
"letterCacheTip": "Recommandé de désactiver, mais après désactivation, il sera impossible de saisir des caractères CJK.",
|
||||
"license": "Licence",
|
||||
"light": "Clair",
|
||||
"loadingFiles": "Chargement des fichiers...",
|
||||
@@ -204,7 +205,6 @@
|
||||
"open": "Ouvrir",
|
||||
"openLastPath": "Ouvrir le dernier chemin",
|
||||
"openLastPathTip": "Les différents serveurs auront des journaux différents, et le journal est le chemin vers la sortie",
|
||||
"parseContainerStats": "Analyser l'état d'occupation du conteneur",
|
||||
"parseContainerStatsTip": "L'analyse de l'occupation des conteneurs Docker est relativement lente.",
|
||||
"paste": "Coller",
|
||||
"path": "Chemin",
|
||||
@@ -264,6 +264,7 @@
|
||||
"serverTabUnkown": "État inconnu",
|
||||
"setting": "Paramètres",
|
||||
"sftpDlPrepare": "Préparation de la connexion...",
|
||||
"sftpEditorTip": "Si vide, utilisez l’éditeur de fichiers intégré de l’application. Si une valeur est présente, utilisez l’éditeur du serveur distant, par exemple `vim` (il est recommandé de détecter automatiquement selon `EDITOR`).",
|
||||
"sftpRmrDirSummary": "Utilisez `rm -r` pour supprimer un dossier en SFTP.",
|
||||
"sftpSSHConnected": "SFTP Connecté",
|
||||
"sftpShowFoldersFirst": "Afficher d'abord les dossiers",
|
||||
@@ -278,6 +279,7 @@
|
||||
"sshTip": "Cette fonctionnalité est actuellement à l'étape expérimentale.\n\nVeuillez signaler les bugs sur {url} ou rejoindre notre développement.",
|
||||
"sshVirtualKeyAutoOff": "Activation automatique des touches virtuelles",
|
||||
"start": "Démarrer",
|
||||
"stat": "Statistiques",
|
||||
"stats": "Statistiques",
|
||||
"stop": "Arrêter",
|
||||
"stopped": "Arrêté",
|
||||
@@ -340,5 +342,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."
|
||||
}
|
||||
@@ -92,6 +92,7 @@
|
||||
"editor": "Editor",
|
||||
"editorHighlightTip": "Performa penyorotan kode saat ini lebih buruk, dan dapat dimatikan secara opsional untuk perbaikan.",
|
||||
"encode": "Menyandi",
|
||||
"envVars": "Variabel lingkungan",
|
||||
"error": "Kesalahan",
|
||||
"exampleName": "Nama contoh",
|
||||
"experimentalFeature": "Fitur eksperimental",
|
||||
@@ -154,7 +155,7 @@
|
||||
"lastTry": "Percobaan terakhir",
|
||||
"launchPage": "Halaman peluncuran",
|
||||
"letterCache": "Caching huruf",
|
||||
"letterCacheTip": "Disarankan untuk diaktifkan, tetapi mengaktifkannya mencegah input karakter CJK (Cina, Jepang, Korea)",
|
||||
"letterCacheTip": "Direkomendasikan untuk menonaktifkan, tetapi setelah dinonaktifkan, tidak mungkin untuk memasukkan karakter CJK.",
|
||||
"license": "Lisensi",
|
||||
"light": "Terang",
|
||||
"loadingFiles": "Memuat file ...",
|
||||
@@ -204,7 +205,6 @@
|
||||
"open": "Membuka",
|
||||
"openLastPath": "Buka jalur terakhir",
|
||||
"openLastPathTip": "Server yang berbeda akan memiliki catatan yang berbeda, dan catatan tersebut adalah jalur menuju pintu keluar",
|
||||
"parseContainerStats": "Memecahkan status okupansi kontainer",
|
||||
"parseContainerStatsTip": "Parsing status okupansi oleh Docker agak lambat",
|
||||
"paste": "Tempel",
|
||||
"path": "Jalur",
|
||||
@@ -264,6 +264,7 @@
|
||||
"serverTabUnkown": "Negara yang tidak diketahui",
|
||||
"setting": "Pengaturan",
|
||||
"sftpDlPrepare": "Bersiap untuk terhubung ...",
|
||||
"sftpEditorTip": "Jika kosong, gunakan editor file bawaan aplikasi. Jika ada nilai, gunakan editor server jarak jauh, misalnya `vim` (disarankan untuk mendeteksi secara otomatis sesuai `EDITOR`).",
|
||||
"sftpRmrDirSummary": "Gunakan `rm -r` untuk menghapus dir di SFTP",
|
||||
"sftpSSHConnected": "Sftp terhubung",
|
||||
"sftpShowFoldersFirst": "Folder ditampilkan lebih dulu",
|
||||
@@ -278,6 +279,7 @@
|
||||
"sshTip": "Fungsi ini sekarang dalam tahap eksperimen.\n\nHarap laporkan bug di {url} atau bergabunglah dengan pengembangan kami.",
|
||||
"sshVirtualKeyAutoOff": "Switching Otomatis Kunci Virtual",
|
||||
"start": "Awal",
|
||||
"stat": "Statistik",
|
||||
"stats": "Statistik",
|
||||
"stop": "Berhenti",
|
||||
"stopped": "dihentikan",
|
||||
@@ -340,5 +342,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."
|
||||
}
|
||||
@@ -92,6 +92,7 @@
|
||||
"editor": "エディター",
|
||||
"editorHighlightTip": "現在のコードハイライトのパフォーマンスはかなり悪いため、改善するために無効にすることを選択できます。",
|
||||
"encode": "エンコード",
|
||||
"envVars": "環境変数",
|
||||
"error": "エラー",
|
||||
"exampleName": "名前例",
|
||||
"experimentalFeature": "実験的な機能",
|
||||
@@ -154,7 +155,7 @@
|
||||
"lastTry": "最後の試み",
|
||||
"launchPage": "起動ページ",
|
||||
"letterCache": "文字キャッシング",
|
||||
"letterCacheTip": "有効化を推奨しますが、有効にするとCJK(中国語、日本語、韓国語)の文字の入力ができなくなります",
|
||||
"letterCacheTip": "無効にすることを推奨しますが、無効にした後はCJK文字を入力することができなくなります。",
|
||||
"license": "オープンソースライセンス",
|
||||
"light": "ライト",
|
||||
"loadingFiles": "ディレクトリを読み込んでいます...",
|
||||
@@ -204,7 +205,6 @@
|
||||
"open": "開く",
|
||||
"openLastPath": "最後のパスを開く",
|
||||
"openLastPathTip": "異なるサーバーには異なる記録があり、記録されているのは退出時のパスです",
|
||||
"parseContainerStats": "コンテナ使用状況を解析",
|
||||
"parseContainerStatsTip": "Dockerの使用状況の解析は比較的遅いです",
|
||||
"paste": "貼り付け",
|
||||
"path": "パス",
|
||||
@@ -264,6 +264,7 @@
|
||||
"serverTabUnkown": "不明な状態",
|
||||
"setting": "設定",
|
||||
"sftpDlPrepare": "サーバーへの接続を準備中...",
|
||||
"sftpEditorTip": "空の場合は、アプリ内蔵のファイルエディタを使用します。値がある場合は、リモートサーバーのエディタ(例:`vim`)を使用します(`EDITOR` に従って自動検出することをお勧めします)。",
|
||||
"sftpRmrDirSummary": "SFTPで`rm -r`を使用してフォルダーを削除",
|
||||
"sftpSSHConnected": "SFTPに接続されました...",
|
||||
"sftpShowFoldersFirst": "フォルダーを先に表示",
|
||||
@@ -278,6 +279,7 @@
|
||||
"sshTip": "この機能は現在テスト段階にあります。\n\n問題がある場合は、{url}でフィードバックしてください。",
|
||||
"sshVirtualKeyAutoOff": "仮想キーの自動オフ",
|
||||
"start": "開始",
|
||||
"stat": "統計",
|
||||
"stats": "統計",
|
||||
"stop": "停止",
|
||||
"stopped": "停止しました",
|
||||
@@ -340,5 +342,6 @@
|
||||
"willTakEeffectImmediately": "変更は即座に有効になります",
|
||||
"wolTip": "WOL(Wake-on-LAN)を設定した後、サーバーに接続するたびにWOLリクエストが送信されます。",
|
||||
"write": "書き込み",
|
||||
"writeScriptFailTip": "スクリプトの書き込みに失敗しました。権限がないかディレクトリが存在しない可能性があります。"
|
||||
"writeScriptFailTip": "スクリプトの書き込みに失敗しました。権限がないかディレクトリが存在しない可能性があります。",
|
||||
"writeScriptTip": "サーバーに接続すると、システムの状態を監視するためのスクリプトが ~/.config/server_box に書き込まれます。スクリプトの内容を確認できます。"
|
||||
}
|
||||
@@ -92,6 +92,7 @@
|
||||
"editor": "Editor",
|
||||
"editorHighlightTip": "De huidige codehighlighting-prestaties zijn slechter en kunnen optioneel worden uitgeschakeld om te verbeteren.",
|
||||
"encode": "Coderen",
|
||||
"envVars": "Omgevingsvariabele",
|
||||
"error": "Fout",
|
||||
"exampleName": "Voorbeeldnaam",
|
||||
"experimentalFeature": "Experimentele functie",
|
||||
@@ -154,7 +155,7 @@
|
||||
"lastTry": "Laatste poging",
|
||||
"launchPage": "Startpagina",
|
||||
"letterCache": "Lettercaching",
|
||||
"letterCacheTip": "Aanbevolen om in te schakelen, maar bij inschakeling wordt de invoer van CJK (Chinees, Japans, Koreaans) tekens voorkomen",
|
||||
"letterCacheTip": "Aanbevolen om uit te schakelen, maar na het uitschakelen is het niet mogelijk om CJK-tekens in te voeren.",
|
||||
"license": "Licentie",
|
||||
"light": "Licht",
|
||||
"loadingFiles": "Bestanden laden...",
|
||||
@@ -204,7 +205,6 @@
|
||||
"open": "Openen",
|
||||
"openLastPath": "Open het laatste pad",
|
||||
"openLastPathTip": "Verschillende servers hebben verschillende logs, en de log is het pad naar de uitgang",
|
||||
"parseContainerStats": "Parseer de containerbezettingstatus",
|
||||
"parseContainerStatsTip": "Het parsen van de bezettingsstatus van Docker is relatief langzaam.",
|
||||
"paste": "Plakken",
|
||||
"path": "Pad",
|
||||
@@ -264,6 +264,7 @@
|
||||
"serverTabUnkown": "Onbekende status",
|
||||
"setting": "Instellingen",
|
||||
"sftpDlPrepare": "Voorbereiden om verbinding te maken...",
|
||||
"sftpEditorTip": "Indien leeg, gebruik de ingebouwde bestandseditor van de app. Indien een waarde aanwezig is, gebruik de editor van de externe server, bijvoorbeeld `vim` (aanbevolen om automatisch te detecteren volgens `EDITOR`).",
|
||||
"sftpRmrDirSummary": "Gebruik `rm -r` om een map te verwijderen in SFTP.",
|
||||
"sftpSSHConnected": "SFTP Verbonden",
|
||||
"sftpShowFoldersFirst": "Mappen eerst weergeven",
|
||||
@@ -278,6 +279,7 @@
|
||||
"sshTip": "Deze functie bevindt zich momenteel in de experimentele fase.\n\nMeld alstublieft bugs op {url} of sluit je aan bij onze ontwikkeling.",
|
||||
"sshVirtualKeyAutoOff": "Automatisch schakelen van virtuele toetsen",
|
||||
"start": "Starten",
|
||||
"stat": "Statistieken",
|
||||
"stats": "Statistieken",
|
||||
"stop": "Stoppen",
|
||||
"stopped": "Gestopt",
|
||||
@@ -340,5 +342,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."
|
||||
}
|
||||
@@ -92,6 +92,7 @@
|
||||
"editor": "Editor",
|
||||
"editorHighlightTip": "O desempenho do destaque de código atualmente é ruim, pode optar por desativá-lo para melhorar.",
|
||||
"encode": "Codificar",
|
||||
"envVars": "Variável de ambiente",
|
||||
"error": "Erro",
|
||||
"exampleName": "Exemplo de nome",
|
||||
"experimentalFeature": "Recurso experimental",
|
||||
@@ -154,7 +155,7 @@
|
||||
"lastTry": "Última tentativa",
|
||||
"launchPage": "Página de lançamento",
|
||||
"letterCache": "Cache de letras",
|
||||
"letterCacheTip": "Recomendado para ativar, mas a ativação impede a entrada de caracteres CJK (chinês, japonês, coreano)",
|
||||
"letterCacheTip": "Recomendado desativar, mas após desativar, será impossível inserir caracteres CJK.",
|
||||
"license": "Licença de código aberto",
|
||||
"light": "Claro",
|
||||
"loadingFiles": "Carregando diretórios...",
|
||||
@@ -204,7 +205,6 @@
|
||||
"open": "Abrir",
|
||||
"openLastPath": "Abrir o último caminho",
|
||||
"openLastPathTip": "Registros diferentes para servidores diferentes, e registra o caminho ao sair",
|
||||
"parseContainerStats": "Analisar status de uso do contêiner",
|
||||
"parseContainerStatsTip": "Análise de status do Docker pode ser lenta",
|
||||
"paste": "Colar",
|
||||
"path": "Caminho",
|
||||
@@ -264,6 +264,7 @@
|
||||
"serverTabUnkown": "Estado desconhecido",
|
||||
"setting": "Configurações",
|
||||
"sftpDlPrepare": "Preparando para conectar ao servidor...",
|
||||
"sftpEditorTip": "Se vazio, use o editor de arquivos integrado do aplicativo. Se houver um valor, use o editor do servidor remoto, por exemplo, `vim` (recomendado detectar automaticamente de acordo com `EDITOR`).",
|
||||
"sftpRmrDirSummary": "Usar `rm -r` em SFTP para excluir pastas",
|
||||
"sftpSSHConnected": "SFTP conectado...",
|
||||
"sftpShowFoldersFirst": "Mostrar pastas primeiro",
|
||||
@@ -278,6 +279,7 @@
|
||||
"sshTip": "Esta funcionalidade está em fase de teste.\n\nPor favor, reporte problemas em {url} ou junte-se a nós no desenvolvimento.",
|
||||
"sshVirtualKeyAutoOff": "Desativação automática das teclas virtuais",
|
||||
"start": "Iniciar",
|
||||
"stat": "Estatísticas",
|
||||
"stats": "Estatísticas",
|
||||
"stop": "Parar",
|
||||
"stopped": "Parado",
|
||||
@@ -340,5 +342,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."
|
||||
}
|
||||
@@ -92,6 +92,7 @@
|
||||
"editor": "редактор",
|
||||
"editorHighlightTip": "Текущая производительность подсветки кода неудовлетворительна, можно отключить для улучшения.",
|
||||
"encode": "кодировать",
|
||||
"envVars": "Переменная окружения",
|
||||
"error": "ошибка",
|
||||
"exampleName": "пример имени",
|
||||
"experimentalFeature": "экспериментальная функция",
|
||||
@@ -154,7 +155,7 @@
|
||||
"lastTry": "последняя попытка",
|
||||
"launchPage": "стартовая страница",
|
||||
"letterCache": "Кэширование букв",
|
||||
"letterCacheTip": "Рекомендуется включить, но его активация препятствует вводу символов CJK (китайский, японский, корейский)",
|
||||
"letterCacheTip": "Рекомендуется отключить, но после отключения будет невозможно вводить символы CJK.",
|
||||
"license": "лицензия",
|
||||
"light": "светлая",
|
||||
"loadingFiles": "Загрузка файлов...",
|
||||
@@ -204,7 +205,6 @@
|
||||
"open": "открыть",
|
||||
"openLastPath": "открыть последний путь",
|
||||
"openLastPathTip": "Для разных серверов будут сохранены разные записи, записывается путь при выходе",
|
||||
"parseContainerStats": "анализ статуса использования контейнера",
|
||||
"parseContainerStatsTip": "Анализ статуса использования Docker может быть медленным",
|
||||
"paste": "вставить",
|
||||
"path": "путь",
|
||||
@@ -264,6 +264,7 @@
|
||||
"serverTabUnkown": "неизвестно",
|
||||
"setting": "настройки",
|
||||
"sftpDlPrepare": "Подготовка к подключению к серверу...",
|
||||
"sftpEditorTip": "Если пусто, используйте встроенный редактор файлов приложения. Если значение указано, используйте редактор удаленного сервера, например, `vim` (рекомендуется автоматически определять согласно `EDITOR`).",
|
||||
"sftpRmrDirSummary": "Использовать `rm -r` в SFTP для удаления папок",
|
||||
"sftpSSHConnected": "SFTP подключен...",
|
||||
"sftpShowFoldersFirst": "показывать папки в начале",
|
||||
@@ -278,6 +279,7 @@
|
||||
"sshTip": "Эта функция находится в стадии тестирования.\n\nПожалуйста, отправляйте отчеты о проблемах на {url} или присоединяйтесь к нашей разработке.",
|
||||
"sshVirtualKeyAutoOff": "автоматическое отключение виртуальных клавиш",
|
||||
"start": "старт",
|
||||
"stat": "Статистика",
|
||||
"stats": "статистика",
|
||||
"stop": "остановить",
|
||||
"stopped": "остановлено",
|
||||
@@ -340,5 +342,6 @@
|
||||
"willTakEeffectImmediately": "Изменения вступят в силу немедленно",
|
||||
"wolTip": "После настройки WOL (Wake-on-LAN) при каждом подключении к серверу отправляется запрос WOL.",
|
||||
"write": "запись",
|
||||
"writeScriptFailTip": "Запись в скрипт не удалась, возможно, из-за отсутствия прав или директории не существует."
|
||||
"writeScriptFailTip": "Запись в скрипт не удалась, возможно, из-за отсутствия прав или директории не существует.",
|
||||
"writeScriptTip": "После подключения к серверу скрипт будет записан в ~/.config/server_box для мониторинга состояния системы. Вы можете проверить содержимое скрипта."
|
||||
}
|
||||
@@ -92,6 +92,7 @@
|
||||
"editor": "编辑器",
|
||||
"editorHighlightTip": "目前的代码高亮性能较为糟糕,可以选择关闭以改善。",
|
||||
"encode": "编码",
|
||||
"envVars": "环境变量",
|
||||
"error": "错误",
|
||||
"exampleName": "名称示例",
|
||||
"experimentalFeature": "实验性功能",
|
||||
@@ -154,7 +155,7 @@
|
||||
"lastTry": "最后尝试",
|
||||
"launchPage": "启动页",
|
||||
"letterCache": "输入法字符缓存",
|
||||
"letterCacheTip": "推荐开启,但是开启后无法输入 CJK 等文字",
|
||||
"letterCacheTip": "推荐关闭,但是关闭后无法输入 CJK 等文字",
|
||||
"license": "开源许可证",
|
||||
"light": "亮",
|
||||
"loadingFiles": "正在加载目录。。。",
|
||||
@@ -204,7 +205,6 @@
|
||||
"open": "打开",
|
||||
"openLastPath": "打开上次的路径",
|
||||
"openLastPathTip": "不同的服务器会有不同的记录,且记录的是退出时的路径",
|
||||
"parseContainerStats": "解析容器占用状态",
|
||||
"parseContainerStatsTip": "Docker 解析占用状态较为缓慢",
|
||||
"paste": "粘贴",
|
||||
"path": "路径",
|
||||
@@ -264,6 +264,7 @@
|
||||
"serverTabUnkown": "未知状态",
|
||||
"setting": "设置",
|
||||
"sftpDlPrepare": "准备连接至服务器...",
|
||||
"sftpEditorTip": "如果为空, 使用App内置的文件编辑器. 如果有值, 这是用远程服务器的编辑器, 例如 `vim` (建议根据 `EDITOR` 自动获取).",
|
||||
"sftpRmrDirSummary": "在 SFTP 中使用 `rm -r` 来删除文件夹",
|
||||
"sftpSSHConnected": "SFTP 已连接...",
|
||||
"sftpShowFoldersFirst": "文件夹显示在前",
|
||||
@@ -278,6 +279,7 @@
|
||||
"sshTip": "该功能目前处于测试阶段。\n\n请在 {url} 反馈问题,或者加入我们开发。",
|
||||
"sshVirtualKeyAutoOff": "虚拟按键自动切换",
|
||||
"start": "开始",
|
||||
"stat": "统计",
|
||||
"stats": "统计",
|
||||
"stop": "停止",
|
||||
"stopped": "已停止",
|
||||
@@ -340,5 +342,6 @@
|
||||
"willTakEeffectImmediately": "更改将会立即生效",
|
||||
"wolTip": "在配置 WOL 后,每次连接服务器都会先发送一次 WOL 请求",
|
||||
"write": "写",
|
||||
"writeScriptFailTip": "写入脚本失败,可能是没有权限/目录不存在等"
|
||||
"writeScriptFailTip": "写入脚本失败,可能是没有权限/目录不存在等",
|
||||
"writeScriptTip": "在连接服务器后,会向 ~/.config/server_box 写入脚本来监测系统状态,你可以审查脚本内容。"
|
||||
}
|
||||
@@ -92,6 +92,7 @@
|
||||
"editor": "編輯器",
|
||||
"editorHighlightTip": "目前的代碼高亮性能較為糟糕,可以選擇關閉以改善。",
|
||||
"encode": "編碼",
|
||||
"envVars": "環境變量",
|
||||
"error": "錯誤",
|
||||
"exampleName": "名稱範例",
|
||||
"experimentalFeature": "實驗性功能",
|
||||
@@ -154,7 +155,7 @@
|
||||
"lastTry": "最後嘗試",
|
||||
"launchPage": "啓動頁",
|
||||
"letterCache": "输入法字符緩存",
|
||||
"letterCacheTip": "推薦開啟,但是開啟後無法輸入CJK(中文、日文、韓文)等文字",
|
||||
"letterCacheTip": "建議關閉,但關閉後將無法輸入 CJK 等文字。",
|
||||
"license": "開源許可證",
|
||||
"light": "亮",
|
||||
"loadingFiles": "正在加載目錄...",
|
||||
@@ -204,7 +205,6 @@
|
||||
"open": "打開",
|
||||
"openLastPath": "打開上次的路徑",
|
||||
"openLastPathTip": "不同的伺服器會有不同的記錄,且記錄的是退出時的路徑",
|
||||
"parseContainerStats": "解析容器佔用狀態",
|
||||
"parseContainerStatsTip": "Docker 解析佔用狀態較為緩慢",
|
||||
"paste": "貼上",
|
||||
"path": "路徑",
|
||||
@@ -264,6 +264,7 @@
|
||||
"serverTabUnkown": "未知狀態",
|
||||
"setting": "設置",
|
||||
"sftpDlPrepare": "準備連接至伺服器...",
|
||||
"sftpEditorTip": "如果為空, 使用App內置的文件編輯器。如果有值, 則使用遠程伺服器的編輯器, 例如 `vim`(建議根據 `EDITOR` 自動獲取)。",
|
||||
"sftpRmrDirSummary": "在 SFTP 中使用 `rm -r` 來刪除文件夾",
|
||||
"sftpSSHConnected": "SFTP 已連接...",
|
||||
"sftpShowFoldersFirst": "資料夾顯示在前",
|
||||
@@ -278,6 +279,7 @@
|
||||
"sshTip": "該功能目前處於測試階段。\n\n請在 {url} 反饋問題,或者加入我們開發。",
|
||||
"sshVirtualKeyAutoOff": "虛擬按鍵自動切換",
|
||||
"start": "開始",
|
||||
"stat": "統計",
|
||||
"stats": "統計",
|
||||
"stop": "停止",
|
||||
"stopped": "已停止",
|
||||
@@ -340,5 +342,6 @@
|
||||
"willTakEeffectImmediately": "更改將會立即生效",
|
||||
"wolTip": "在配置 WOL(網絡喚醒)後,每次連接伺服器都會先發送一次 WOL 請求。",
|
||||
"write": "写",
|
||||
"writeScriptFailTip": "寫入腳本失敗,可能是沒有權限/目錄不存在等。"
|
||||
"writeScriptFailTip": "寫入腳本失敗,可能是沒有權限/目錄不存在等。",
|
||||
"writeScriptTip": "連接到伺服器後,將會在 ~/.config/server_box 中寫入一個腳本來監測系統狀態。你可以審查腳本內容。"
|
||||
}
|
||||
@@ -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';
|
||||
@@ -54,9 +53,7 @@ void _runInZone(void Function() body) {
|
||||
|
||||
runZonedGuarded(
|
||||
body,
|
||||
(obj, trace) {
|
||||
Loggers.root.warning(obj, null, trace);
|
||||
},
|
||||
(e, s) => print('[ZONE] $e\n$s'),
|
||||
zoneSpecification: zoneSpec,
|
||||
);
|
||||
}
|
||||
@@ -83,7 +80,6 @@ Future<void> _initApp() async {
|
||||
}
|
||||
|
||||
Future<void> _initData() async {
|
||||
// await SecureStore.init();
|
||||
await Hive.initFlutter();
|
||||
// Ordered by typeId
|
||||
Hive.registerAdapter(PrivateKeyInfoAdapter()); // 1
|
||||
@@ -95,6 +91,8 @@ Future<void> _initData() async {
|
||||
Hive.registerAdapter(ServerCustomAdapter()); // 7
|
||||
Hive.registerAdapter(WakeOnLanCfgAdapter()); // 8
|
||||
|
||||
await PrefStore.init(); // Call this before accessing any store
|
||||
|
||||
await Stores.setting.init();
|
||||
await Stores.server.init();
|
||||
await Stores.key.init();
|
||||
@@ -121,8 +119,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();
|
||||
}
|
||||
|
||||
@@ -225,6 +225,7 @@ class BackupPage extends StatelessWidget {
|
||||
final backup = await context.showLoadingDialog(
|
||||
fn: () => Computer.shared.start(Backup.fromJsonString, text.trim()),
|
||||
);
|
||||
if (backup == null) return;
|
||||
if (backupFormatVersion != backup.version) {
|
||||
context.showSnackBar(l10n.backupVersionNotMatch);
|
||||
return;
|
||||
@@ -305,6 +306,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 +317,22 @@ class BackupPage extends StatelessWidget {
|
||||
label: 'URL',
|
||||
hint: 'https://example.com/webdav/',
|
||||
controller: url,
|
||||
suggestion: false,
|
||||
onSubmitted: (p0) => FocusScope.of(context).requestFocus(nodeUser),
|
||||
),
|
||||
Input(
|
||||
label: l10n.user,
|
||||
controller: user,
|
||||
node: nodeUser,
|
||||
suggestion: false,
|
||||
onSubmitted: (p0) => FocusScope.of(context).requestFocus(nodePwd),
|
||||
),
|
||||
Input(
|
||||
label: l10n.pwd,
|
||||
controller: pwd,
|
||||
node: nodePwd,
|
||||
suggestion: false,
|
||||
onSubmitted: (_) => context.pop(true),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -357,6 +368,7 @@ class BackupPage extends StatelessWidget {
|
||||
fn: () => Computer.shared.start(Backup.fromJsonString, text.trim()),
|
||||
);
|
||||
|
||||
if (backup == null) return;
|
||||
if (backupFormatVersion != backup.version) {
|
||||
context.showSnackBar(l10n.backupVersionNotMatch);
|
||||
return;
|
||||
@@ -398,6 +410,7 @@ class BackupPage extends StatelessWidget {
|
||||
return list.map((e) => ServerPrivateInfo.fromJson(e)).toList();
|
||||
}, text.trim()),
|
||||
);
|
||||
if (spis == null) return;
|
||||
final sure = await context.showRoundDialog<bool>(
|
||||
title: l10n.import,
|
||||
child: Text(l10n.askContinue('${spis.length} ${l10n.server}')),
|
||||
@@ -409,13 +422,15 @@ class BackupPage extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
if (sure == true) {
|
||||
await context.showLoadingDialog(
|
||||
final suc = await context.showLoadingDialog(
|
||||
fn: () async {
|
||||
for (var spi in spis) {
|
||||
Stores.server.put(spi);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
);
|
||||
if (suc != true) return;
|
||||
context.showSnackBar(l10n.success);
|
||||
}
|
||||
} catch (e, s) {
|
||||
|
||||
@@ -338,18 +338,21 @@ class _ContainerPageState extends State<ContainerPage> {
|
||||
label: l10n.image,
|
||||
hint: 'xxx:1.1',
|
||||
controller: imageCtrl,
|
||||
suggestion: false,
|
||||
),
|
||||
Input(
|
||||
type: TextInputType.text,
|
||||
controller: nameCtrl,
|
||||
label: l10n.containerName,
|
||||
hint: 'xxx',
|
||||
suggestion: false,
|
||||
),
|
||||
Input(
|
||||
type: TextInputType.text,
|
||||
controller: argsCtrl,
|
||||
label: l10n.extraArgs,
|
||||
hint: '-p 2222:22 -v ~/.xxx/:/xxx',
|
||||
suggestion: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -425,6 +428,7 @@ class _ContainerPageState extends State<ContainerPage> {
|
||||
controller: ctrl,
|
||||
onSubmitted: _onSaveDockerHost,
|
||||
hint: 'unix:///run/user/1000/docker.sock',
|
||||
suggestion: false,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
|
||||
@@ -145,10 +145,10 @@ class _EditorPageState extends State<EditorPage> {
|
||||
// If path is not null, then it's a file editor
|
||||
// save the text and return true to pop the page
|
||||
if (widget.path != null) {
|
||||
await context.showLoadingDialog(
|
||||
final res = await context.showLoadingDialog(
|
||||
fn: () => File(widget.path!).writeAsString(_controller.text),
|
||||
);
|
||||
|
||||
if (res == null) return;
|
||||
context.pop(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,11 +78,7 @@ class _HomePageState extends State<HomePage>
|
||||
|
||||
switch (state) {
|
||||
case AppLifecycleState.resumed:
|
||||
if (_shouldAuth) {
|
||||
if (Stores.setting.useBioAuth.fetch()) {
|
||||
BioAuth.go().then((_) => _shouldAuth = false);
|
||||
}
|
||||
}
|
||||
if (_shouldAuth) _goAuth();
|
||||
if (!Pros.server.isAutoRefreshOn) {
|
||||
Pros.server.startAutoRefresh();
|
||||
}
|
||||
@@ -323,7 +319,7 @@ ${GithubIds.participants.map((e) => '[$e](${e.url})').join(' ')}
|
||||
@override
|
||||
Future<void> afterFirstLayout(BuildContext context) async {
|
||||
// Auth required for first launch
|
||||
if (Stores.setting.useBioAuth.fetch()) BioAuth.go();
|
||||
_goAuth();
|
||||
|
||||
//_reqNotiPerm();
|
||||
|
||||
@@ -361,6 +357,16 @@ ${GithubIds.participants.map((e) => '[$e](${e.url})').join(' ')}
|
||||
// }
|
||||
// }
|
||||
|
||||
void _goAuth() {
|
||||
if (Stores.setting.useBioAuth.fetch()) {
|
||||
if (BioAuthPage.route.isAlreadyIn) return;
|
||||
BioAuthPage.route.go(
|
||||
context,
|
||||
args: BioAuthPageArgs(onAuthSuccess: () => _shouldAuth = false),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onLongPressSetting() async {
|
||||
final map = Stores.setting.box.toJson(includeInternal: false);
|
||||
final keys = map.keys;
|
||||
|
||||
@@ -52,12 +52,14 @@ class _IPerfPageState extends State<IPerfPage> {
|
||||
controller: _hostCtrl,
|
||||
label: l10n.host,
|
||||
icon: Icons.computer,
|
||||
suggestion: false,
|
||||
),
|
||||
Input(
|
||||
controller: _portCtrl,
|
||||
label: l10n.port,
|
||||
type: TextInputType.number,
|
||||
icon: Icons.numbers,
|
||||
suggestion: false,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -59,6 +59,7 @@ class _PingPageState extends State<PingPage>
|
||||
controller: _textEditingController,
|
||||
hint: l10n.inputDomainHere,
|
||||
maxLines: 1,
|
||||
suggestion: false,
|
||||
onSubmitted: (_) => _doPing(),
|
||||
),
|
||||
actions: [
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
@@ -160,6 +135,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
|
||||
onSubmitted: (_) => _focusScope.requestFocus(_keyNode),
|
||||
label: l10n.name,
|
||||
icon: Icons.info,
|
||||
suggestion: true,
|
||||
),
|
||||
Input(
|
||||
controller: _keyController,
|
||||
@@ -170,6 +146,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
|
||||
onSubmitted: (_) => _focusScope.requestFocus(_pwdNode),
|
||||
label: l10n.privateKey,
|
||||
icon: Icons.vpn_key,
|
||||
suggestion: false,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
@@ -206,6 +183,8 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
|
||||
obscureText: true,
|
||||
label: l10n.pwd,
|
||||
icon: Icons.password,
|
||||
suggestion: false,
|
||||
onSubmitted: (_) => _onTapSave(),
|
||||
),
|
||||
SizedBox(height: MediaQuery.of(context).size.height * 0.1),
|
||||
ValBuilder(
|
||||
@@ -215,4 +194,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,10 +430,9 @@ final class _PvePageState extends State<PvePage> {
|
||||
],
|
||||
);
|
||||
if (sure != true) return;
|
||||
bool? suc;
|
||||
await context.showLoadingDialog(fn: () async {
|
||||
suc = await func(item.node, item.id);
|
||||
});
|
||||
|
||||
final suc =
|
||||
await context.showLoadingDialog(fn: () => func(item.node, item.id));
|
||||
if (suc == true) {
|
||||
context.showSnackBar(l10n.success);
|
||||
} else {
|
||||
|
||||
@@ -198,6 +198,29 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
||||
]);
|
||||
}
|
||||
|
||||
final List<Widget> children = Stores.setting.cpuViewAsProgress.fetch()
|
||||
? _buildCPUProgress(ss.cpu)
|
||||
: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 17, vertical: 13),
|
||||
child: SizedBox(
|
||||
height: 137,
|
||||
width: _media.size.width - 26 - 34,
|
||||
child: _buildLineChart(
|
||||
ss.cpu.spots,
|
||||
//ss.cpu.rangeX,
|
||||
tooltipPrefix: 'CPU',
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
if (ss.cpu.brand.isNotEmpty) {
|
||||
children.add(Column(
|
||||
children: ss.cpu.brand.entries.map(_buildCpuModelItem).toList(),
|
||||
).paddingOnly(top: 13));
|
||||
}
|
||||
|
||||
return CardX(
|
||||
child: ExpandTile(
|
||||
title: Align(
|
||||
@@ -214,27 +237,30 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: details,
|
||||
),
|
||||
children: Stores.setting.cpuViewAsProgress.fetch()
|
||||
? _buildCPUProgress(ss.cpu)
|
||||
: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 17, vertical: 13),
|
||||
child: SizedBox(
|
||||
height: 137,
|
||||
width: _media.size.width - 26 - 34,
|
||||
child: _buildLineChart(
|
||||
ss.cpu.spots,
|
||||
//ss.cpu.rangeX,
|
||||
tooltipPrefix: 'CPU',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCpuModelItem(MapEntry<String, int> e) {
|
||||
final child = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: _media.size.width * .7,
|
||||
child: Text(
|
||||
e.key,
|
||||
style: UIs.text13,
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
Text('x ${e.value}', style: UIs.text13Grey),
|
||||
],
|
||||
);
|
||||
return child.paddingSymmetric(horizontal: 17);
|
||||
}
|
||||
|
||||
Widget _buildDetailPercent(double percent, String timeType) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -874,6 +900,6 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
||||
|
||||
bool _getInitExpand(int len, [int? max]) {
|
||||
if (!_collapse) return true;
|
||||
return len <= (max ?? 3);
|
||||
return len > 0 && len <= (max ?? 3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:icons_plus/icons_plus.dart';
|
||||
@@ -22,7 +20,7 @@ class ServerEditPage extends StatefulWidget {
|
||||
State<ServerEditPage> createState() => _ServerEditPageState();
|
||||
}
|
||||
|
||||
class _ServerEditPageState extends State<ServerEditPage> {
|
||||
class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
final _nameController = TextEditingController();
|
||||
final _ipController = TextEditingController();
|
||||
final _altUrlController = TextEditingController();
|
||||
@@ -30,7 +28,6 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
final _usernameController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
final _pveAddrCtrl = TextEditingController();
|
||||
final _customCmdCtrl = TextEditingController();
|
||||
final _preferTempDevCtrl = TextEditingController();
|
||||
final _logoUrlCtrl = TextEditingController();
|
||||
final _wolMacCtrl = TextEditingController();
|
||||
@@ -49,58 +46,11 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
final _autoConnect = ValueNotifier(true);
|
||||
final _jumpServer = ValueNotifier<String?>(null);
|
||||
final _pveIgnoreCert = ValueNotifier(false);
|
||||
final _env = <String, String>{}.vn;
|
||||
final _customCmds = <String, String>{}.vn;
|
||||
|
||||
var _tags = <String>[];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final spi = widget.spi;
|
||||
if (spi != null) {
|
||||
_nameController.text = spi.name;
|
||||
_ipController.text = spi.ip;
|
||||
_portController.text = spi.port.toString();
|
||||
_usernameController.text = spi.user;
|
||||
if (spi.keyId == null) {
|
||||
_passwordController.text = spi.pwd ?? '';
|
||||
} else {
|
||||
_keyIdx.value = Pros.key.pkis.indexWhere(
|
||||
(e) => e.id == widget.spi!.keyId,
|
||||
);
|
||||
}
|
||||
|
||||
/// List in dart is passed by pointer, so you need to copy it here
|
||||
_tags.addAll(spi.tags ?? []);
|
||||
|
||||
_altUrlController.text = spi.alterUrl ?? '';
|
||||
_autoConnect.value = spi.autoConnect ?? true;
|
||||
_jumpServer.value = spi.jumpId;
|
||||
|
||||
final custom = spi.custom;
|
||||
if (custom != null) {
|
||||
_pveAddrCtrl.text = custom.pveAddr ?? '';
|
||||
_pveIgnoreCert.value = custom.pveIgnoreCert;
|
||||
try {
|
||||
// Add a null check here to prevent setting `null` to the controller
|
||||
final encoded = json.encode(custom.cmds!);
|
||||
if (encoded.isNotEmpty) {
|
||||
_customCmdCtrl.text = encoded;
|
||||
}
|
||||
} catch (_) {}
|
||||
_preferTempDevCtrl.text = custom.preferTempDev ?? '';
|
||||
_logoUrlCtrl.text = custom.logoUrl ?? '';
|
||||
}
|
||||
|
||||
final wol = spi.wolCfg;
|
||||
if (wol != null) {
|
||||
_wolMacCtrl.text = wol.mac;
|
||||
_wolIpCtrl.text = wol.ip;
|
||||
_wolPwdCtrl.text = wol.pwd ?? '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
@@ -116,7 +66,6 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
_portFocus.dispose();
|
||||
_usernameFocus.dispose();
|
||||
_pveAddrCtrl.dispose();
|
||||
_customCmdCtrl.dispose();
|
||||
_preferTempDevCtrl.dispose();
|
||||
_logoUrlCtrl.dispose();
|
||||
_wolMacCtrl.dispose();
|
||||
@@ -132,10 +81,13 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: _buildAppBar(),
|
||||
body: _buildForm(),
|
||||
floatingActionButton: _buildFAB(),
|
||||
return GestureDetector(
|
||||
onTap: () => _focusScope.unfocus(),
|
||||
child: Scaffold(
|
||||
appBar: _buildAppBar(),
|
||||
body: _buildForm(),
|
||||
floatingActionButton: _buildFAB(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -174,6 +126,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
|
||||
Widget _buildForm() {
|
||||
final children = [
|
||||
_buildWriteScriptTip(),
|
||||
Input(
|
||||
autoFocus: true,
|
||||
controller: _nameController,
|
||||
@@ -195,6 +148,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
label: l10n.host,
|
||||
icon: BoxIcons.bx_server,
|
||||
hint: 'example.com',
|
||||
suggestion: false,
|
||||
),
|
||||
Input(
|
||||
controller: _portController,
|
||||
@@ -204,6 +158,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
label: l10n.port,
|
||||
icon: Bootstrap.number_123,
|
||||
hint: '22',
|
||||
suggestion: false,
|
||||
),
|
||||
Input(
|
||||
controller: _usernameController,
|
||||
@@ -213,14 +168,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
label: l10n.user,
|
||||
icon: Icons.account_box,
|
||||
hint: 'root',
|
||||
),
|
||||
Input(
|
||||
controller: _altUrlController,
|
||||
type: TextInputType.url,
|
||||
node: _alterUrlFocus,
|
||||
label: l10n.fallbackSshDest,
|
||||
icon: MingCute.link_line,
|
||||
hint: 'user@ip:port',
|
||||
suggestion: false,
|
||||
),
|
||||
TagEditor(
|
||||
tags: _tags,
|
||||
@@ -245,7 +193,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
_buildMore(),
|
||||
];
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(17, 17, 17, 47),
|
||||
padding: const EdgeInsets.fromLTRB(17, 7, 17, 47),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -287,6 +235,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
label: l10n.pwd,
|
||||
icon: Icons.password,
|
||||
hint: l10n.pwd,
|
||||
suggestion: false,
|
||||
onSubmitted: (_) => _onSave(),
|
||||
));
|
||||
}
|
||||
@@ -296,12 +245,13 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
}
|
||||
|
||||
Widget _buildKeyAuth() {
|
||||
const padding = EdgeInsets.only(left: 23, right: 13);
|
||||
return Consumer<PrivateKeyProvider>(
|
||||
builder: (_, key, __) {
|
||||
final tiles = List<Widget>.generate(key.pkis.length, (index) {
|
||||
final e = key.pkis[index];
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 17),
|
||||
contentPadding: padding,
|
||||
leading: Text(
|
||||
'#${index + 1}',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
|
||||
@@ -323,7 +273,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text(l10n.addPrivateKey),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 17),
|
||||
contentPadding: padding,
|
||||
trailing: const Padding(
|
||||
padding: EdgeInsets.only(right: 13),
|
||||
child: Icon(Icons.add),
|
||||
@@ -341,19 +291,45 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEnvs() {
|
||||
return _env.listenVal((val) {
|
||||
final subtitle =
|
||||
val.isEmpty ? null : Text(val.keys.join(','), style: UIs.textGrey);
|
||||
return ListTile(
|
||||
leading: const Padding(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Icon(HeroIcons.variable),
|
||||
),
|
||||
subtitle: subtitle,
|
||||
title: Text(l10n.envVars),
|
||||
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||
onTap: () async {
|
||||
final res = await KvEditor.route.go(
|
||||
context,
|
||||
args: KvEditorArgs(data: widget.spi?.envs ?? {}),
|
||||
);
|
||||
if (res == null) return;
|
||||
_env.value = res;
|
||||
},
|
||||
).cardx;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildMore() {
|
||||
return ExpandTile(
|
||||
title: Text(l10n.more),
|
||||
children: [
|
||||
const Text('Logo', style: UIs.text13Grey),
|
||||
UIs.height7,
|
||||
Input(
|
||||
controller: _logoUrlCtrl,
|
||||
type: TextInputType.url,
|
||||
icon: Icons.image,
|
||||
label: 'URL',
|
||||
label: 'Logo URL',
|
||||
hint: 'https://example.com/logo.png',
|
||||
suggestion: false,
|
||||
),
|
||||
_buildAltUrl(),
|
||||
_buildEnvs(),
|
||||
UIs.height7,
|
||||
..._buildPVEs(),
|
||||
UIs.height7,
|
||||
@@ -367,6 +343,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
label: l10n.deviceName,
|
||||
icon: MingCute.low_temperature_line,
|
||||
hint: 'nvme-pci-0400',
|
||||
suggestion: false,
|
||||
),
|
||||
UIs.height7,
|
||||
..._buildWOLs(),
|
||||
@@ -374,6 +351,18 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAltUrl() {
|
||||
return Input(
|
||||
controller: _altUrlController,
|
||||
type: TextInputType.url,
|
||||
node: _alterUrlFocus,
|
||||
label: l10n.fallbackSshDest,
|
||||
icon: MingCute.link_line,
|
||||
hint: 'user@ip:port',
|
||||
suggestion: false,
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildPVEs() {
|
||||
const addr = 'https://127.0.0.1:8006';
|
||||
return [
|
||||
@@ -395,6 +384,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
node: node,
|
||||
label: 'URL',
|
||||
hint: addr,
|
||||
suggestion: false,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
@@ -421,14 +411,26 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
return [
|
||||
Text(l10n.customCmd, style: UIs.text13Grey),
|
||||
UIs.height7,
|
||||
Input(
|
||||
controller: _customCmdCtrl,
|
||||
type: TextInputType.text,
|
||||
maxLines: 3,
|
||||
label: 'JSON',
|
||||
icon: Icons.code,
|
||||
hint: '{${l10n.customCmdHint}}',
|
||||
),
|
||||
_customCmds.listenVal(
|
||||
(vals) {
|
||||
return ListTile(
|
||||
leading: const Icon(BoxIcons.bxs_file_json).paddingOnly(left: 10),
|
||||
title: const Text('JSON'),
|
||||
subtitle: vals.isEmpty
|
||||
? null
|
||||
: Text(vals.keys.join(','), style: UIs.textGrey),
|
||||
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||
onTap: () async {
|
||||
final res = await KvEditor.route.go(
|
||||
context,
|
||||
args: KvEditorArgs(data: _customCmds.value),
|
||||
);
|
||||
if (res == null) return;
|
||||
_customCmds.value = res;
|
||||
},
|
||||
);
|
||||
},
|
||||
).cardx,
|
||||
ListTile(
|
||||
leading: const Padding(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
@@ -459,6 +461,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
label: 'MAC ${l10n.addr}',
|
||||
icon: Icons.computer,
|
||||
hint: '00:11:22:33:44:55',
|
||||
suggestion: false,
|
||||
),
|
||||
Input(
|
||||
controller: _wolIpCtrl,
|
||||
@@ -466,6 +469,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
label: 'IP ${l10n.addr}',
|
||||
icon: Icons.network_cell,
|
||||
hint: '192.168.1.x',
|
||||
suggestion: false,
|
||||
),
|
||||
Input(
|
||||
controller: _wolPwdCtrl,
|
||||
@@ -474,6 +478,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
label: l10n.pwd,
|
||||
icon: Icons.password,
|
||||
hint: l10n.pwd,
|
||||
suggestion: false,
|
||||
),
|
||||
];
|
||||
}
|
||||
@@ -565,19 +570,11 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
if (_portController.text.isEmpty) {
|
||||
_portController.text = '22';
|
||||
}
|
||||
final customCmds = () {
|
||||
if (_customCmdCtrl.text.isEmpty) return null;
|
||||
try {
|
||||
return json.decode(_customCmdCtrl.text).cast<String, String>();
|
||||
} catch (e) {
|
||||
context.showSnackBar(l10n.invalidJson);
|
||||
return null;
|
||||
}
|
||||
}();
|
||||
final customCmds = _customCmds.value;
|
||||
final custom = ServerCustom(
|
||||
pveAddr: _pveAddrCtrl.text.selfIfNotNullEmpty,
|
||||
pveIgnoreCert: _pveIgnoreCert.value,
|
||||
cmds: customCmds,
|
||||
cmds: customCmds.isEmpty ? null : customCmds,
|
||||
preferTempDev: _preferTempDevCtrl.text.selfIfNotNullEmpty,
|
||||
logoUrl: _logoUrlCtrl.text.selfIfNotNullEmpty,
|
||||
);
|
||||
@@ -617,19 +614,9 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
jumpId: _jumpServer.value,
|
||||
custom: custom,
|
||||
wolCfg: wol,
|
||||
envs: _env.value.isEmpty ? null : _env.value,
|
||||
);
|
||||
|
||||
// final tipShown = Stores.history.writeScriptTipShown;
|
||||
// if (!tipShown.fetch()) {
|
||||
// final ok = await context.showRoundDialog(
|
||||
// title: l10n.attention,
|
||||
// child: SimpleMarkdown(data: l10n.beforeConnect(Urls.thisRepo)),
|
||||
// actions: Btns.oks(onTap: () => context.pop(true)),
|
||||
// );
|
||||
// if (ok != true) return;
|
||||
// tipShown.put(true);
|
||||
// }
|
||||
|
||||
if (widget.spi == null) {
|
||||
Pros.server.addServer(spi);
|
||||
} else {
|
||||
@@ -638,4 +625,70 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
|
||||
context.pop();
|
||||
}
|
||||
|
||||
Widget _buildWriteScriptTip() {
|
||||
return Center(
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
onTap: () {
|
||||
context.showRoundDialog(
|
||||
title: l10n.attention,
|
||||
child: SimpleMarkdown(data: l10n.writeScriptTip),
|
||||
actions: Btns.oks(onTap: () => context.pop(true)),
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.tips_and_updates, size: 15, color: Colors.grey),
|
||||
UIs.width13,
|
||||
Text(l10n.attention, style: UIs.textGrey)
|
||||
],
|
||||
).paddingSymmetric(horizontal: 13, vertical: 3),
|
||||
),
|
||||
).paddingOnly(bottom: 13);
|
||||
}
|
||||
|
||||
@override
|
||||
void afterFirstLayout(BuildContext context) {
|
||||
final spi = widget.spi;
|
||||
if (spi != null) {
|
||||
_nameController.text = spi.name;
|
||||
_ipController.text = spi.ip;
|
||||
_portController.text = spi.port.toString();
|
||||
_usernameController.text = spi.user;
|
||||
if (spi.keyId == null) {
|
||||
_passwordController.text = spi.pwd ?? '';
|
||||
} else {
|
||||
_keyIdx.value = Pros.key.pkis.indexWhere(
|
||||
(e) => e.id == widget.spi!.keyId,
|
||||
);
|
||||
}
|
||||
|
||||
/// List in dart is passed by pointer, so you need to copy it here
|
||||
_tags.addAll(spi.tags ?? []);
|
||||
|
||||
_altUrlController.text = spi.alterUrl ?? '';
|
||||
_autoConnect.value = spi.autoConnect ?? true;
|
||||
_jumpServer.value = spi.jumpId;
|
||||
|
||||
final custom = spi.custom;
|
||||
if (custom != null) {
|
||||
_pveAddrCtrl.text = custom.pveAddr ?? '';
|
||||
_pveIgnoreCert.value = custom.pveIgnoreCert;
|
||||
_customCmds.value = custom.cmds ?? {};
|
||||
_preferTempDevCtrl.text = custom.preferTempDev ?? '';
|
||||
_logoUrlCtrl.text = custom.logoUrl ?? '';
|
||||
}
|
||||
|
||||
final wol = spi.wolCfg;
|
||||
if (wol != null) {
|
||||
_wolMacCtrl.text = wol.mac;
|
||||
_wolIpCtrl.text = wol.ip;
|
||||
_wolPwdCtrl.text = wol.pwd ?? '';
|
||||
}
|
||||
|
||||
_env.value = spi.envs ?? {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ class ServerPage extends StatefulWidget {
|
||||
State<ServerPage> createState() => _ServerPageState();
|
||||
}
|
||||
|
||||
const _cardPad = 74.0;
|
||||
const _cardPadSingle = 13.0;
|
||||
|
||||
class _ServerPageState extends State<ServerPage>
|
||||
with AutomaticKeepAliveClientMixin, AfterLayoutMixin {
|
||||
late MediaQueryData _media;
|
||||
@@ -97,7 +100,7 @@ class _ServerPageState extends State<ServerPage>
|
||||
),
|
||||
floatingActionButton: AutoHide(
|
||||
key: _autoHideKey,
|
||||
direction: AxisDirection.down,
|
||||
direction: AxisDirection.right,
|
||||
offset: 75,
|
||||
controller: _scrollController,
|
||||
child: FloatingActionButton(
|
||||
@@ -289,7 +292,12 @@ class _ServerPageState extends State<ServerPage>
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 13, horizontal: 7),
|
||||
padding: const EdgeInsets.only(
|
||||
left: _cardPadSingle,
|
||||
right: 3,
|
||||
top: _cardPadSingle,
|
||||
bottom: _cardPadSingle,
|
||||
),
|
||||
child: _buildRealServerCard(srv),
|
||||
),
|
||||
),
|
||||
@@ -299,7 +307,7 @@ class _ServerPageState extends State<ServerPage>
|
||||
/// The child's width mat not equal to 1/4 of the screen width,
|
||||
/// so we need to wrap it with a SizedBox.
|
||||
Widget _wrapWithSizedbox(Widget child, [bool circle = false]) {
|
||||
var width = (_media.size.width - 74) / (circle ? 4 : 4.3);
|
||||
var width = (_media.size.width - _cardPad) / (circle ? 4 : 4.3);
|
||||
if (_useDoubleColumn) width /= 2;
|
||||
return SizedBox(
|
||||
width: width,
|
||||
@@ -340,66 +348,72 @@ class _ServerPageState extends State<ServerPage>
|
||||
}
|
||||
|
||||
List<Widget> _buildFlippedCard(Server srv) {
|
||||
final children = [
|
||||
IconTextBtn(
|
||||
onPressed: () => _askFor(
|
||||
func: () async {
|
||||
if (Stores.setting.showSuspendTip.fetch()) {
|
||||
await context.showRoundDialog(
|
||||
title: l10n.attention,
|
||||
child: Text(l10n.suspendTip),
|
||||
);
|
||||
Stores.setting.showSuspendTip.put(false);
|
||||
}
|
||||
srv.client?.execWithPwd(
|
||||
ShellFunc.suspend.exec,
|
||||
context: context,
|
||||
id: srv.id,
|
||||
);
|
||||
},
|
||||
typ: l10n.suspend,
|
||||
name: srv.spi.name,
|
||||
),
|
||||
icon: Icons.stop,
|
||||
text: l10n.suspend,
|
||||
),
|
||||
IconTextBtn(
|
||||
onPressed: () => _askFor(
|
||||
func: () => srv.client?.execWithPwd(
|
||||
ShellFunc.shutdown.exec,
|
||||
context: context,
|
||||
id: srv.id,
|
||||
),
|
||||
typ: l10n.shutdown,
|
||||
name: srv.spi.name,
|
||||
),
|
||||
icon: Icons.power_off,
|
||||
text: l10n.shutdown,
|
||||
),
|
||||
IconTextBtn(
|
||||
onPressed: () => _askFor(
|
||||
func: () => srv.client?.execWithPwd(
|
||||
ShellFunc.reboot.exec,
|
||||
context: context,
|
||||
id: srv.id,
|
||||
),
|
||||
typ: l10n.reboot,
|
||||
name: srv.spi.name,
|
||||
),
|
||||
icon: Icons.restart_alt,
|
||||
text: l10n.reboot,
|
||||
),
|
||||
IconTextBtn(
|
||||
onPressed: () => AppRoutes.serverEdit(spi: srv.spi).go(context),
|
||||
icon: Icons.edit,
|
||||
text: l10n.edit,
|
||||
)
|
||||
];
|
||||
|
||||
final width = (_media.size.width - _cardPad) / children.length;
|
||||
return [
|
||||
UIs.height13,
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
IconTextBtn(
|
||||
onPressed: () => _askFor(
|
||||
func: () async {
|
||||
if (Stores.setting.showSuspendTip.fetch()) {
|
||||
await context.showRoundDialog(
|
||||
title: l10n.attention,
|
||||
child: Text(l10n.suspendTip),
|
||||
);
|
||||
Stores.setting.showSuspendTip.put(false);
|
||||
}
|
||||
srv.client?.execWithPwd(
|
||||
ShellFunc.suspend.exec,
|
||||
context: context,
|
||||
id: srv.id,
|
||||
);
|
||||
},
|
||||
typ: l10n.suspend,
|
||||
name: srv.spi.name,
|
||||
),
|
||||
icon: Icons.stop,
|
||||
text: l10n.suspend,
|
||||
),
|
||||
IconTextBtn(
|
||||
onPressed: () => _askFor(
|
||||
func: () => srv.client?.execWithPwd(
|
||||
ShellFunc.shutdown.exec,
|
||||
context: context,
|
||||
id: srv.id,
|
||||
),
|
||||
typ: l10n.shutdown,
|
||||
name: srv.spi.name,
|
||||
),
|
||||
icon: Icons.power_off,
|
||||
text: l10n.shutdown,
|
||||
),
|
||||
IconTextBtn(
|
||||
onPressed: () => _askFor(
|
||||
func: () => srv.client?.execWithPwd(
|
||||
ShellFunc.reboot.exec,
|
||||
context: context,
|
||||
id: srv.id,
|
||||
),
|
||||
typ: l10n.reboot,
|
||||
name: srv.spi.name,
|
||||
),
|
||||
icon: Icons.restart_alt,
|
||||
text: l10n.reboot,
|
||||
),
|
||||
IconTextBtn(
|
||||
onPressed: () => AppRoutes.serverEdit(spi: srv.spi).go(context),
|
||||
icon: Icons.edit,
|
||||
text: l10n.edit,
|
||||
)
|
||||
],
|
||||
)
|
||||
children: children.map((e) {
|
||||
if (width == 0) return e;
|
||||
return SizedBox(width: width, child: e);
|
||||
}).toList(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -458,13 +472,9 @@ class _ServerPageState extends State<ServerPage>
|
||||
}
|
||||
|
||||
Widget _buildTopRightWidget(Server s) {
|
||||
return switch (s.conn) {
|
||||
ServerConn.connecting ||
|
||||
ServerConn.loading ||
|
||||
ServerConn.connected =>
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 11, right: 3),
|
||||
child: SizedBox(
|
||||
final (child, onTap) = switch (s.conn) {
|
||||
ServerConn.connecting || ServerConn.loading || ServerConn.connected => (
|
||||
SizedBox(
|
||||
width: 19,
|
||||
height: 19,
|
||||
child: CircularProgressIndicator(
|
||||
@@ -472,46 +482,55 @@ class _ServerPageState extends State<ServerPage>
|
||||
valueColor: AlwaysStoppedAnimation(UIs.primaryColor),
|
||||
),
|
||||
),
|
||||
null,
|
||||
),
|
||||
ServerConn.failed => InkWell(
|
||||
onTap: () {
|
||||
ServerConn.failed => (
|
||||
const Icon(
|
||||
Icons.refresh,
|
||||
size: 21,
|
||||
color: Colors.grey,
|
||||
),
|
||||
() {
|
||||
TryLimiter.reset(s.spi.id);
|
||||
Pros.server.refresh(spi: s.spi);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(left: 11, right: 3),
|
||||
child: Icon(
|
||||
Icons.refresh,
|
||||
size: 21,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
ServerConn.disconnected => InkWell(
|
||||
onTap: () => Pros.server.refresh(spi: s.spi),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(left: 11, right: 3),
|
||||
child: Icon(
|
||||
BoxIcons.bx_link,
|
||||
size: 21,
|
||||
color: Colors.grey,
|
||||
),
|
||||
ServerConn.disconnected => (
|
||||
const Icon(
|
||||
BoxIcons.bx_link,
|
||||
size: 21,
|
||||
color: Colors.grey,
|
||||
),
|
||||
() => Pros.server.refresh(spi: s.spi)
|
||||
),
|
||||
ServerConn.finished => InkWell(
|
||||
onTap: () => Pros.server.closeServer(id: s.spi.id),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(left: 11, right: 3),
|
||||
child: Icon(
|
||||
BoxIcons.bx_unlink,
|
||||
size: 17,
|
||||
color: Colors.grey,
|
||||
),
|
||||
ServerConn.finished => (
|
||||
const Icon(
|
||||
BoxIcons.bx_unlink,
|
||||
size: 17,
|
||||
color: Colors.grey,
|
||||
),
|
||||
() => Pros.server.closeServer(id: s.spi.id),
|
||||
),
|
||||
_ when Stores.setting.serverTabUseOldUI.fetch() => (
|
||||
ServerFuncBtnsTopRight(spi: s.spi),
|
||||
null,
|
||||
),
|
||||
_ when Stores.setting.serverTabUseOldUI.fetch() =>
|
||||
ServerFuncBtnsTopRight(spi: s.spi),
|
||||
};
|
||||
|
||||
// Or the loading icon will be rescaled.
|
||||
final wrapped = child is SizedBox
|
||||
? child
|
||||
: SizedBox(
|
||||
height: _kCardHeightMin,
|
||||
width: 27,
|
||||
child: child,
|
||||
);
|
||||
if (onTap == null) return wrapped.paddingOnly(left: 10);
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(7),
|
||||
onTap: onTap,
|
||||
child: wrapped,
|
||||
).paddingOnly(left: 10);
|
||||
}
|
||||
|
||||
Widget _buildTopRightText(Server s) {
|
||||
@@ -646,20 +665,25 @@ ${ss.err?.message ?? l10n.unknownError}
|
||||
_tag == null || (pro.pick(id: e)?.spi.tags?.contains(_tag) ?? false))
|
||||
.toList();
|
||||
|
||||
static const _kCardHeightMin = 23.0;
|
||||
static const _kCardHeightFlip = 99.0;
|
||||
static const _kCardHeightNormal = 108.0;
|
||||
static const _kCardHeightMoveOutFuncs = 135.0;
|
||||
|
||||
double? _calcCardHeight(ServerConn cs, bool flip) {
|
||||
if (_textFactorDouble != 1.0) return null;
|
||||
if (cs != ServerConn.finished) {
|
||||
return 23.0;
|
||||
return _kCardHeightMin;
|
||||
}
|
||||
if (flip) {
|
||||
return 97.0;
|
||||
return _kCardHeightFlip;
|
||||
}
|
||||
if (Stores.setting.moveOutServerTabFuncBtns.fetch() &&
|
||||
// Discussion #146
|
||||
!Stores.setting.serverTabUseOldUI.fetch()) {
|
||||
return 132;
|
||||
return _kCardHeightMoveOutFuncs;
|
||||
}
|
||||
return 106;
|
||||
return _kCardHeightNormal;
|
||||
}
|
||||
|
||||
void _askFor({
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_highlight/theme_map.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:icons_plus/icons_plus.dart';
|
||||
import 'package:locale_names/locale_names.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/res/rebuild.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
@@ -270,6 +271,7 @@ class _SettingPageState extends State<SettingPage> {
|
||||
controller: ctrl,
|
||||
hint: '#8b2252',
|
||||
icon: Icons.colorize,
|
||||
suggestion: false,
|
||||
),
|
||||
ColorPicker(
|
||||
color: Color(_setting.primaryColor.fetch()),
|
||||
@@ -472,8 +474,12 @@ class _SettingPageState extends State<SettingPage> {
|
||||
Widget _buildTermFontSize() {
|
||||
return ListTile(
|
||||
leading: const Icon(MingCute.font_size_line),
|
||||
title: Text(l10n.fontSize),
|
||||
subtitle: Text(l10n.termFontSizeTip, style: UIs.textGrey),
|
||||
// title: Text(l10n.fontSize),
|
||||
// subtitle: Text(l10n.termFontSizeTip, style: UIs.textGrey),
|
||||
title: TipText(
|
||||
tip: l10n.termFontSizeTip,
|
||||
text: l10n.fontSize,
|
||||
),
|
||||
trailing: ValBuilder(
|
||||
listenable: _setting.termFontSize.listenable(),
|
||||
builder: (val) => Text(
|
||||
@@ -530,7 +536,7 @@ class _SettingPageState extends State<SettingPage> {
|
||||
final selected = await context.showPickSingleDialog(
|
||||
title: l10n.language,
|
||||
items: AppLocalizations.supportedLocales,
|
||||
name: (p0) => p0.code,
|
||||
name: (p0) => '${p0.nativeDisplayLanguage} (${p0.code})',
|
||||
initial: _setting.locale.fetch().toLocale,
|
||||
);
|
||||
if (selected != null) {
|
||||
@@ -605,8 +611,12 @@ class _SettingPageState extends State<SettingPage> {
|
||||
Widget _buildFullScreenSwitch() {
|
||||
return ListTile(
|
||||
leading: const Icon(Bootstrap.phone_landscape_fill),
|
||||
title: Text(l10n.fullScreen),
|
||||
subtitle: Text(l10n.fullScreenTip, style: UIs.textGrey),
|
||||
// title: Text(l10n.fullScreen),
|
||||
// subtitle: Text(l10n.fullScreenTip, style: UIs.textGrey),
|
||||
title: TipText(
|
||||
tip: l10n.fullScreenTip,
|
||||
text: l10n.fullScreen,
|
||||
),
|
||||
trailing: StoreSwitch(
|
||||
prop: _setting.fullScreen,
|
||||
callback: (_) => RNodes.app.notify(),
|
||||
@@ -681,6 +691,7 @@ class _SettingPageState extends State<SettingPage> {
|
||||
Widget _buildSFTP() {
|
||||
return Column(
|
||||
children: [
|
||||
_buildSftpEditor(),
|
||||
_buildSftpRmrDir(),
|
||||
_buildSftpOpenLastPath(),
|
||||
_buildSftpShowFoldersFirst(),
|
||||
@@ -691,8 +702,12 @@ class _SettingPageState extends State<SettingPage> {
|
||||
Widget _buildSftpOpenLastPath() {
|
||||
return ListTile(
|
||||
leading: const Icon(MingCute.history_line),
|
||||
title: Text(l10n.openLastPath),
|
||||
subtitle: Text(l10n.openLastPathTip, style: UIs.textGrey),
|
||||
// title: Text(l10n.openLastPath),
|
||||
// subtitle: Text(l10n.openLastPathTip, style: UIs.textGrey),
|
||||
title: TipText(
|
||||
tip: l10n.openLastPathTip,
|
||||
text: l10n.openLastPath,
|
||||
),
|
||||
trailing: StoreSwitch(prop: _setting.sftpOpenLastPath),
|
||||
);
|
||||
}
|
||||
@@ -766,8 +781,12 @@ class _SettingPageState extends State<SettingPage> {
|
||||
Widget _buildTextScaler() {
|
||||
final ctrl = TextEditingController(text: _setting.textFactor.toString());
|
||||
return ListTile(
|
||||
title: Text(l10n.textScaler),
|
||||
subtitle: Text(l10n.textScalerTip, style: UIs.textGrey),
|
||||
// title: Text(l10n.textScaler),
|
||||
// subtitle: Text(l10n.textScalerTip, style: UIs.textGrey),
|
||||
title: TipText(
|
||||
tip: l10n.textScalerTip,
|
||||
text: l10n.textScaler,
|
||||
),
|
||||
trailing: ValBuilder(
|
||||
listenable: _setting.textFactor.listenable(),
|
||||
builder: (val) => Text(
|
||||
@@ -784,6 +803,7 @@ class _SettingPageState extends State<SettingPage> {
|
||||
icon: Icons.format_size,
|
||||
controller: ctrl,
|
||||
onSubmitted: _onSaveTextScaler,
|
||||
suggestion: false,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
@@ -819,8 +839,12 @@ class _SettingPageState extends State<SettingPage> {
|
||||
|
||||
Widget _buildServerFuncBtnsSwitch() {
|
||||
return ListTile(
|
||||
title: Text(l10n.location),
|
||||
subtitle: Text(l10n.moveOutServerFuncBtnsHelp, style: UIs.text13Grey),
|
||||
// title: Text(l10n.location),
|
||||
// subtitle: Text(l10n.moveOutServerFuncBtnsHelp, style: UIs.text13Grey),
|
||||
title: TipText(
|
||||
tip: l10n.moveOutServerFuncBtnsHelp,
|
||||
text: l10n.location,
|
||||
),
|
||||
trailing: StoreSwitch(prop: _setting.moveOutServerTabFuncBtns),
|
||||
);
|
||||
}
|
||||
@@ -888,6 +912,7 @@ class _SettingPageState extends State<SettingPage> {
|
||||
autoFocus: true,
|
||||
type: TextInputType.number,
|
||||
icon: Icons.font_download,
|
||||
suggestion: false,
|
||||
onSubmitted: (_) => onSave(),
|
||||
),
|
||||
actions: [
|
||||
@@ -910,8 +935,12 @@ class _SettingPageState extends State<SettingPage> {
|
||||
|
||||
Widget _buildDoubleColumnServersPage() {
|
||||
return ListTile(
|
||||
title: Text(l10n.doubleColumnMode),
|
||||
subtitle: Text(l10n.doubleColumnTip, style: UIs.textGrey),
|
||||
// title: Text(l10n.doubleColumnMode),
|
||||
// subtitle: Text(l10n.doubleColumnTip, style: UIs.textGrey),
|
||||
title: TipText(
|
||||
tip: l10n.doubleColumnTip,
|
||||
text: l10n.doubleColumnMode,
|
||||
),
|
||||
trailing: StoreSwitch(prop: _setting.doubleColumnServersPage),
|
||||
);
|
||||
}
|
||||
@@ -934,8 +963,12 @@ class _SettingPageState extends State<SettingPage> {
|
||||
Widget _buildEditorHighlight() {
|
||||
return ListTile(
|
||||
leading: const Icon(MingCute.code_line, size: _kIconSize),
|
||||
title: Text(l10n.highlight),
|
||||
subtitle: Text(l10n.editorHighlightTip, style: UIs.textGrey),
|
||||
// title: Text(l10n.highlight),
|
||||
// subtitle: Text(l10n.editorHighlightTip, style: UIs.textGrey),
|
||||
title: TipText(
|
||||
tip: l10n.editorHighlightTip,
|
||||
text: l10n.highlight,
|
||||
),
|
||||
trailing: StoreSwitch(prop: _setting.editorHighlight),
|
||||
);
|
||||
}
|
||||
@@ -958,9 +991,11 @@ class _SettingPageState extends State<SettingPage> {
|
||||
|
||||
Widget _buildContainerTrySudo() {
|
||||
return ListTile(
|
||||
leading: const Icon(Clarity.administrator_solid),
|
||||
title: Text(l10n.trySudo),
|
||||
subtitle: Text(l10n.containerTrySudoTip, style: UIs.textGrey),
|
||||
leading: const Icon(EvaIcons.person_done),
|
||||
title: TipText(
|
||||
tip: l10n.containerTrySudoTip,
|
||||
text: l10n.trySudo,
|
||||
),
|
||||
trailing: StoreSwitch(prop: _setting.containerTrySudo),
|
||||
);
|
||||
}
|
||||
@@ -975,9 +1010,13 @@ class _SettingPageState extends State<SettingPage> {
|
||||
|
||||
Widget _buildContainerParseStat() {
|
||||
return ListTile(
|
||||
leading: const Icon(IonIcons.stats_chart, size: _kIconSize),
|
||||
title: Text(l10n.parseContainerStats),
|
||||
subtitle: Text(l10n.parseContainerStatsTip, style: UIs.textGrey),
|
||||
leading: const Icon(MingCute.chart_line_line, size: _kIconSize),
|
||||
// title: Text(l10n.parseContainerStats),
|
||||
// subtitle: Text(l10n.parseContainerStatsTip, style: UIs.textGrey),
|
||||
title: TipText(
|
||||
tip: l10n.parseContainerStatsTip,
|
||||
text: l10n.stat,
|
||||
),
|
||||
trailing: StoreSwitch(prop: _setting.containerParseStat),
|
||||
);
|
||||
}
|
||||
@@ -999,8 +1038,12 @@ class _SettingPageState extends State<SettingPage> {
|
||||
|
||||
Widget _buildRememberPwdInMem() {
|
||||
return ListTile(
|
||||
title: Text(l10n.rememberPwdInMem),
|
||||
subtitle: Text(l10n.rememberPwdInMemTip, style: UIs.textGrey),
|
||||
// title: Text(l10n.rememberPwdInMem),
|
||||
// subtitle: Text(l10n.rememberPwdInMemTip, style: UIs.textGrey),
|
||||
title: TipText(
|
||||
tip: l10n.rememberPwdInMemTip,
|
||||
text: l10n.rememberPwdInMem,
|
||||
),
|
||||
trailing: StoreSwitch(prop: _setting.rememberPwdInMem),
|
||||
);
|
||||
}
|
||||
@@ -1151,6 +1194,7 @@ class _SettingPageState extends State<SettingPage> {
|
||||
hint: 'https://example.com/logo.png',
|
||||
icon: Icons.link,
|
||||
maxLines: 2,
|
||||
suggestion: false,
|
||||
onSubmitted: onSave,
|
||||
),
|
||||
ListTile(
|
||||
@@ -1181,13 +1225,53 @@ class _SettingPageState extends State<SettingPage> {
|
||||
|
||||
Widget _buildLetterCache() {
|
||||
return ListTile(
|
||||
leading: const Icon(Bootstrap.input_cursor),
|
||||
title: Text(l10n.letterCache),
|
||||
subtitle: Text(
|
||||
'${l10n.letterCacheTip}\n${l10n.needRestart}',
|
||||
style: UIs.textGrey,
|
||||
leading: const Icon(Bootstrap.alphabet),
|
||||
// title: Text(l10n.letterCache),
|
||||
// subtitle: Text(
|
||||
// '${l10n.letterCacheTip}\n${l10n.needRestart}',
|
||||
// style: UIs.textGrey,
|
||||
// ),
|
||||
title: TipText(
|
||||
tip: '${l10n.letterCacheTip}\n${l10n.needRestart}',
|
||||
text: l10n.letterCache,
|
||||
),
|
||||
trailing: StoreSwitch(prop: _setting.letterCache),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSftpEditor() {
|
||||
return _setting.sftpEditor.listenable().listenVal(
|
||||
(val) {
|
||||
return ListTile(
|
||||
leading: const Icon(MingCute.edit_fill),
|
||||
title: TipText(text: l10n.editor, tip: l10n.sftpEditorTip),
|
||||
trailing: Text(
|
||||
val.isEmpty ? l10n.inner : val,
|
||||
style: UIs.text15,
|
||||
),
|
||||
onTap: () async {
|
||||
final ctrl = TextEditingController(text: val);
|
||||
void onSave(String s) {
|
||||
_setting.sftpEditor.put(s);
|
||||
context.pop();
|
||||
}
|
||||
|
||||
await context.showRoundDialog<bool>(
|
||||
title: l10n.choose,
|
||||
child: Input(
|
||||
controller: ctrl,
|
||||
autoFocus: true,
|
||||
label: l10n.editor,
|
||||
hint: '\$EDITOR / vim / nano ...',
|
||||
icon: Icons.edit,
|
||||
suggestion: false,
|
||||
onSubmitted: onSave,
|
||||
),
|
||||
actions: Btns.oks(onTap: () => onSave(ctrl.text)),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,9 @@ class _AndroidSettingsPageState extends State<AndroidSettingsPage> {
|
||||
|
||||
Widget _buildBgRun() {
|
||||
return ListTile(
|
||||
title: Text(l10n.bgRun),
|
||||
subtitle: Text(l10n.bgRunTip, style: UIs.textGrey),
|
||||
// title: Text(l10n.bgRun),
|
||||
// subtitle: Text(l10n.bgRunTip, style: UIs.textGrey),
|
||||
title: TipText(text: l10n.bgRun, tip: l10n.bgRunTip),
|
||||
trailing: StoreSwitch(prop: Stores.setting.bgRun),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -114,13 +114,8 @@ class _IOSSettingsPageState extends State<IOSSettingsPage> {
|
||||
final result = await AppRoutes.kvEditor(data: urls).go(context);
|
||||
if (result == null || result is! Map<String, String>) return;
|
||||
|
||||
try {
|
||||
await context.showLoadingDialog(fn: () async {
|
||||
await wc.updateApplicationContext({'urls': result});
|
||||
});
|
||||
} catch (e, s) {
|
||||
context.showErrDialog(e: e, s: s, operation: 'Watch Context');
|
||||
Loggers.app.warning('Update watch config failed', e, s);
|
||||
}
|
||||
await context.showLoadingDialog(fn: () async {
|
||||
await wc.updateApplicationContext({'urls': result});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class _ServerDetailOrderPageState extends State<ServerFuncBtnsOrderPage> {
|
||||
text: TextSpan(
|
||||
children: [
|
||||
WidgetSpan(child: Icon(funcBtn.icon)),
|
||||
const WidgetSpan(child: UIs.width7),
|
||||
const WidgetSpan(child: UIs.width13),
|
||||
TextSpan(text: funcBtn.toStr, style: UIs.textGrey),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -114,6 +114,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
||||
onSubmitted: (_) => FocusScope.of(context).requestFocus(_scriptNode),
|
||||
label: l10n.name,
|
||||
icon: Icons.info,
|
||||
suggestion: true,
|
||||
),
|
||||
Input(
|
||||
controller: _noteController,
|
||||
@@ -122,6 +123,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
||||
type: TextInputType.multiline,
|
||||
label: l10n.note,
|
||||
icon: Icons.note,
|
||||
suggestion: true,
|
||||
),
|
||||
ValBuilder(
|
||||
listenable: _tags,
|
||||
@@ -146,6 +148,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
||||
type: TextInputType.multiline,
|
||||
label: l10n.snippet,
|
||||
icon: Icons.code,
|
||||
suggestion: false,
|
||||
),
|
||||
_buildAutoRunOn(),
|
||||
_buildTip(),
|
||||
|
||||
@@ -147,7 +147,7 @@ class SSHPageState extends State<SSHPage>
|
||||
controller: _terminalController,
|
||||
keyboardType:
|
||||
letterCache ? TextInputType.text : TextInputType.visiblePassword,
|
||||
enableSuggestions: !letterCache,
|
||||
enableSuggestions: letterCache,
|
||||
textStyle: _terminalStyle,
|
||||
theme: _terminalTheme,
|
||||
deleteDetection: isMobile,
|
||||
@@ -393,12 +393,13 @@ class SSHPageState extends State<SSHPage>
|
||||
width: _terminal.viewWidth,
|
||||
height: _terminal.viewHeight,
|
||||
),
|
||||
environment: widget.spi.envs,
|
||||
);
|
||||
|
||||
//_setupDiscontinuityTimer();
|
||||
|
||||
if (session == null) {
|
||||
_writeLn('Null session');
|
||||
_writeLn('Null session, please back and retry\r\n');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -329,6 +329,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
||||
child: Input(
|
||||
autoFocus: true,
|
||||
controller: TextEditingController(text: fileName),
|
||||
suggestion: true,
|
||||
onSubmitted: (p0) {
|
||||
context.pop();
|
||||
final newPath = '${file.parent.path}/$p0';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
@@ -212,7 +213,8 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
context.showSnackBar('remote path is null');
|
||||
return;
|
||||
}
|
||||
final remotePath = '$remoteDir/${path.split('/').last}';
|
||||
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),
|
||||
@@ -267,6 +269,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
label: l10n.path,
|
||||
node: node,
|
||||
controller: controller,
|
||||
suggestion: true,
|
||||
onSubmitted: (value) => context.pop(value),
|
||||
);
|
||||
},
|
||||
@@ -278,7 +281,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
}
|
||||
|
||||
_status.path?.update(p);
|
||||
final suc = await _listDir();
|
||||
final suc = await _listDir() ?? false;
|
||||
if (suc && Stores.setting.recordHistory.fetch()) {
|
||||
Stores.history.sftpGoPath.add(p);
|
||||
}
|
||||
@@ -405,6 +408,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
},
|
||||
onErr: (e, s) {
|
||||
context.showErrDialog(e: e, s: s, operation: l10n.permission);
|
||||
return false;
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -441,6 +445,18 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
}
|
||||
|
||||
Future<void> _edit(SftpName name) async {
|
||||
context.pop();
|
||||
|
||||
// #489
|
||||
final editor = Stores.setting.sftpEditor.fetch();
|
||||
if (editor.isNotEmpty) {
|
||||
// Use single quote to avoid escape
|
||||
final cmd = "$editor '${_getRemotePath(name)}'";
|
||||
await AppRoutes.ssh(spi: widget.spi, initCmd: cmd).go(context);
|
||||
await _listDir();
|
||||
return;
|
||||
}
|
||||
|
||||
final size = name.attr.size;
|
||||
if (size == null || size > Miscs.editorMaxSize) {
|
||||
context.showSnackBar(l10n.fileTooLarge(
|
||||
@@ -450,7 +466,6 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
));
|
||||
return;
|
||||
}
|
||||
context.pop();
|
||||
|
||||
final remotePath = _getRemotePath(name);
|
||||
final localPath = await _getLocalPath(remotePath);
|
||||
@@ -462,7 +477,8 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
SftpReqType.download,
|
||||
);
|
||||
Pros.sftp.add(req, completer: completer);
|
||||
await context.showLoadingDialog(fn: () => completer.future);
|
||||
final suc = await context.showLoadingDialog(fn: () => completer.future);
|
||||
if (suc == null) return;
|
||||
|
||||
final result = await AppRoutes.editor(path: localPath).go<bool>(context);
|
||||
if (result != null && result) {
|
||||
@@ -553,24 +569,23 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
context.pop();
|
||||
try {
|
||||
await context.showLoadingDialog(
|
||||
fn: () async {
|
||||
final remotePath = _getRemotePath(file);
|
||||
if (useRmr) {
|
||||
await _client!.run('rm -r "$remotePath"');
|
||||
} else if (file.attr.isDirectory) {
|
||||
await _status.client!.rmdir(remotePath);
|
||||
} else {
|
||||
await _status.client!.remove(remotePath);
|
||||
}
|
||||
},
|
||||
onErr: (e, s) {},
|
||||
);
|
||||
_listDir();
|
||||
} catch (e, s) {
|
||||
context.showErrDialog(e: e, s: s, operation: l10n.delete);
|
||||
}
|
||||
|
||||
final suc = await context.showLoadingDialog(
|
||||
fn: () async {
|
||||
final remotePath = _getRemotePath(file);
|
||||
if (useRmr) {
|
||||
await _client!.run('rm -r "$remotePath"');
|
||||
} else if (file.attr.isDirectory) {
|
||||
await _status.client!.rmdir(remotePath);
|
||||
} else {
|
||||
await _status.client!.remove(remotePath);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
);
|
||||
if (suc == null) return;
|
||||
|
||||
_listDir();
|
||||
},
|
||||
child: Text(l10n.delete, style: UIs.textRed),
|
||||
),
|
||||
@@ -581,6 +596,34 @@ 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();
|
||||
|
||||
final suc = await context.showLoadingDialog(
|
||||
fn: () async {
|
||||
final dir = '${_status.path!.path}/${textController.text}';
|
||||
await _status.client!.mkdir(dir);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
if (suc == null) return;
|
||||
|
||||
_listDir();
|
||||
}
|
||||
|
||||
context.showRoundDialog(
|
||||
title: l10n.createFolder,
|
||||
child: Input(
|
||||
@@ -588,6 +631,8 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
icon: Icons.folder,
|
||||
controller: textController,
|
||||
label: l10n.name,
|
||||
suggestion: true,
|
||||
onSubmitted: (_) => onSubmitted(),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
@@ -595,33 +640,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 +650,35 @@ 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();
|
||||
|
||||
final suc = await context.showLoadingDialog(
|
||||
fn: () async {
|
||||
final path = '${_status.path!.path}/${textController.text}';
|
||||
await _client!.run('touch "$path"');
|
||||
return true;
|
||||
},
|
||||
);
|
||||
if (suc == null) return;
|
||||
|
||||
_listDir();
|
||||
}
|
||||
|
||||
context.showRoundDialog(
|
||||
title: l10n.createFile,
|
||||
child: Input(
|
||||
@@ -638,37 +686,12 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
icon: Icons.insert_drive_file,
|
||||
controller: textController,
|
||||
label: l10n.name,
|
||||
suggestion: true,
|
||||
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 +701,35 @@ 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();
|
||||
|
||||
final suc = await context.showLoadingDialog(
|
||||
fn: () async {
|
||||
final newName = textController.text;
|
||||
await _status.client?.rename(file.filename, newName);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
if (suc == null) return;
|
||||
|
||||
_listDir();
|
||||
}
|
||||
|
||||
context.showRoundDialog(
|
||||
title: l10n.rename,
|
||||
child: Input(
|
||||
@@ -685,38 +737,13 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
icon: Icons.abc,
|
||||
controller: textController,
|
||||
label: l10n.name,
|
||||
suggestion: true,
|
||||
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),
|
||||
),
|
||||
],
|
||||
@@ -740,7 +767,10 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
);
|
||||
return;
|
||||
}
|
||||
await context.showLoadingDialog(fn: () async => _client?.run(cmd));
|
||||
final suc = await context.showLoadingDialog(
|
||||
fn: () => _client?.run(cmd) ?? Future.value(false),
|
||||
);
|
||||
if (suc == null) return;
|
||||
_listDir();
|
||||
}
|
||||
|
||||
@@ -755,7 +785,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
}
|
||||
|
||||
/// Only return true if the path is changed
|
||||
Future<bool> _listDir() async {
|
||||
Future<bool?> _listDir() async {
|
||||
return context.showLoadingDialog(
|
||||
fn: () async {
|
||||
_status.client ??= await _client?.sftp();
|
||||
|
||||
@@ -3,19 +3,14 @@ import 'dart:io';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/core/extension/ssh_client.dart';
|
||||
import 'package:server_box/data/model/app/menu/base.dart';
|
||||
import 'package:server_box/data/model/app/menu/server_func.dart';
|
||||
import 'package:server_box/data/model/app/shell_func.dart';
|
||||
import 'package:server_box/data/model/pkg/manager.dart';
|
||||
import 'package:server_box/data/model/server/dist.dart';
|
||||
import 'package:server_box/data/model/server/snippet.dart';
|
||||
import 'package:server_box/data/res/provider.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
import '../../core/route.dart';
|
||||
import '../../core/utils/server.dart';
|
||||
import '../../data/model/pkg/upgrade_info.dart';
|
||||
import '../../data/model/server/server_private_info.dart';
|
||||
|
||||
class ServerFuncBtnsTopRight extends StatelessWidget {
|
||||
@@ -98,9 +93,9 @@ void _onTapMoreBtns(
|
||||
BuildContext context,
|
||||
) async {
|
||||
switch (value) {
|
||||
case ServerFuncBtn.pkg:
|
||||
_onPkg(context, spi);
|
||||
break;
|
||||
// case ServerFuncBtn.pkg:
|
||||
// _onPkg(context, spi);
|
||||
// break;
|
||||
case ServerFuncBtn.sftp:
|
||||
AppRoutes.sftp(spi: spi).checkGo(
|
||||
context: context,
|
||||
@@ -225,80 +220,86 @@ bool _checkClient(BuildContext context, String id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> _onPkg(BuildContext context, ServerPrivateInfo spi) async {
|
||||
final server = spi.server;
|
||||
final client = server?.client;
|
||||
if (server == null || client == null) {
|
||||
context.showSnackBar(l10n.noClient);
|
||||
return;
|
||||
}
|
||||
final sys = server.status.more[StatusCmdType.sys];
|
||||
if (sys == null) {
|
||||
context.showSnackBar(l10n.noResult);
|
||||
return;
|
||||
}
|
||||
// Future<void> _onPkg(BuildContext context, ServerPrivateInfo spi) async {
|
||||
// final server = spi.server;
|
||||
// final client = server?.client;
|
||||
// if (server == null || client == null) {
|
||||
// context.showSnackBar(l10n.noClient);
|
||||
// return;
|
||||
// }
|
||||
// final sys = server.status.more[StatusCmdType.sys];
|
||||
// if (sys == null) {
|
||||
// context.showSnackBar(l10n.noResult);
|
||||
// return;
|
||||
// }
|
||||
|
||||
final pkg = PkgManager.fromDist(sys.dist);
|
||||
if (pkg == null) {
|
||||
context.showSnackBar('Unsupported dist: $sys');
|
||||
return;
|
||||
}
|
||||
// final pkg = PkgManager.fromDist(sys.dist);
|
||||
// if (pkg == null) {
|
||||
// context.showSnackBar('Unsupported dist: $sys');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Update pkg list
|
||||
await context.showLoadingDialog(
|
||||
fn: () async {
|
||||
final updateCmd = pkg.update;
|
||||
if (updateCmd != null) {
|
||||
await client.execWithPwd(
|
||||
updateCmd,
|
||||
context: context,
|
||||
id: spi.id,
|
||||
);
|
||||
}
|
||||
},
|
||||
barrierDismiss: true,
|
||||
);
|
||||
// // Update pkg list
|
||||
// final suc = await context.showLoadingDialog(
|
||||
// fn: () async {
|
||||
// final updateCmd = pkg.update;
|
||||
// if (updateCmd != null) {
|
||||
// await client.execWithPwd(
|
||||
// updateCmd,
|
||||
// context: context,
|
||||
// id: spi.id,
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
// barrierDismiss: true,
|
||||
// );
|
||||
// if (suc != true) return;
|
||||
|
||||
final listCmd = pkg.listUpdate;
|
||||
if (listCmd == null) {
|
||||
context.showSnackBar('Unsupported dist: $sys');
|
||||
return;
|
||||
}
|
||||
// final listCmd = pkg.listUpdate;
|
||||
// if (listCmd == null) {
|
||||
// context.showSnackBar('Unsupported dist: $sys');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Get upgrade list
|
||||
final result = await context.showLoadingDialog(fn: () async {
|
||||
return await client.run(listCmd).string;
|
||||
});
|
||||
final list = pkg.updateListRemoveUnused(result.split('\n'));
|
||||
final upgradeable = list.map((e) => UpgradePkgInfo(e, pkg)).toList();
|
||||
if (upgradeable.isEmpty) {
|
||||
context.showSnackBar(l10n.noUpdateAvailable);
|
||||
return;
|
||||
}
|
||||
final args = upgradeable.map((e) => e.package).join(' ');
|
||||
final isSU = server.spi.user == 'root';
|
||||
final upgradeCmd = isSU ? pkg.upgrade(args) : 'sudo ${pkg.upgrade(args)}';
|
||||
// // Get upgrade list
|
||||
// final result = await context.showLoadingDialog(
|
||||
// fn: () => client.run(listCmd).string,
|
||||
// );
|
||||
// if (result == null || result.isEmpty) {
|
||||
// context.showSnackBar(l10n.noResult);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Confirm upgrade
|
||||
final gotoUpgrade = await context.showRoundDialog<bool>(
|
||||
title: l10n.attention,
|
||||
child: SingleChildScrollView(
|
||||
child: Text(
|
||||
'${l10n.pkgUpgradeTip}\n${l10n.foundNUpdate(upgradeable.length)}\n\n$upgradeCmd'),
|
||||
),
|
||||
actions: [
|
||||
CountDownBtn(
|
||||
onTap: () => context.pop(true),
|
||||
text: l10n.update,
|
||||
afterColor: Colors.red,
|
||||
),
|
||||
],
|
||||
);
|
||||
// final list = pkg.updateListRemoveUnused(result.split('\n'));
|
||||
// final upgradeable = list.map((e) => UpgradePkgInfo(e, pkg)).toList();
|
||||
// if (upgradeable.isEmpty) {
|
||||
// context.showSnackBar(l10n.noUpdateAvailable);
|
||||
// return;
|
||||
// }
|
||||
// final args = upgradeable.map((e) => e.package).join(' ');
|
||||
// final isSU = server.spi.user == 'root';
|
||||
// final upgradeCmd = isSU ? pkg.upgrade(args) : 'sudo ${pkg.upgrade(args)}';
|
||||
|
||||
if (gotoUpgrade != true) return;
|
||||
// // Confirm upgrade
|
||||
// final gotoUpgrade = await context.showRoundDialog<bool>(
|
||||
// title: l10n.attention,
|
||||
// child: SingleChildScrollView(
|
||||
// child: Text(
|
||||
// '${l10n.pkgUpgradeTip}\n${l10n.foundNUpdate(upgradeable.length)}\n\n$upgradeCmd'),
|
||||
// ),
|
||||
// actions: [
|
||||
// CountDownBtn(
|
||||
// onTap: () => context.pop(true),
|
||||
// text: l10n.update,
|
||||
// afterColor: Colors.red,
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
|
||||
AppRoutes.ssh(spi: spi, initCmd: upgradeCmd).checkGo(
|
||||
context: context,
|
||||
check: () => _checkClient(context, spi.id),
|
||||
);
|
||||
}
|
||||
// if (gotoUpgrade != true) return;
|
||||
|
||||
// AppRoutes.ssh(spi: spi, initCmd: upgradeCmd).checkGo(
|
||||
// context: context,
|
||||
// check: () => _checkClient(context, spi.id),
|
||||
// );
|
||||
// }
|
||||
|
||||
@@ -471,7 +471,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1018;
|
||||
CURRENT_PROJECT_VERSION = 1034;
|
||||
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.1034;
|
||||
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 = 1034;
|
||||
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.1034;
|
||||
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 = 1034;
|
||||
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.1034;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "Server Box";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
||||
20
pubspec.lock
20
pubspec.lock
@@ -368,8 +368,8 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
path: "."
|
||||
ref: "v1.0.34"
|
||||
resolved-ref: "9d8afa6b0bc72223213ca30f67c3be2eb129f928"
|
||||
ref: "v1.0.35"
|
||||
resolved-ref: "7964acfe55e3e3f5d5232a0c2371cff5fa7edc4c"
|
||||
url: "https://github.com/lppcg/fl_build.git"
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
@@ -385,8 +385,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "v1.0.72"
|
||||
resolved-ref: "7b57c8e08ff199247bb174d499876627b10f86b5"
|
||||
ref: "v1.0.89"
|
||||
resolved-ref: "2ea7a87e7f4c1bd68902557799a4e9406e559dcf"
|
||||
url: "https://github.com/lppcg/fl_lib"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
@@ -662,10 +662,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.1"
|
||||
version: "0.6.7"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -754,6 +754,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.10"
|
||||
locale_names:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: locale_names
|
||||
sha256: "7a89ca54072f4f13d0f5df5a9ba69337554bf2fd057d1dd2a238898f3f159374"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
19
pubspec.yaml
19
pubspec.yaml
@@ -1,7 +1,7 @@
|
||||
name: server_box
|
||||
description: server status & toolbox app.
|
||||
publish_to: 'none'
|
||||
version: 1.0.1018+1018
|
||||
version: 1.0.1034+1034
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0"
|
||||
@@ -21,7 +21,6 @@ dependencies:
|
||||
code_text_field: ^1.1.0
|
||||
shared_preferences: ^2.1.1
|
||||
dynamic_color: ^1.6.6
|
||||
#flutter_secure_storage: ^9.0.0
|
||||
xml: ^6.4.2 # for parsing nvidia-smi
|
||||
flutter_displaymode: ^0.6.0
|
||||
flutter_background_service: ^5.0.5
|
||||
@@ -31,6 +30,7 @@ dependencies:
|
||||
flutter_adaptive_scaffold: ^0.1.10+2
|
||||
device_info_plus: ^10.1.0
|
||||
extended_image: ^8.2.1
|
||||
locale_names: ^1.1.1
|
||||
dartssh2:
|
||||
git:
|
||||
url: https://github.com/lollipopkit/dartssh2
|
||||
@@ -62,15 +62,17 @@ dependencies:
|
||||
fl_lib:
|
||||
git:
|
||||
url: https://github.com/lppcg/fl_lib
|
||||
ref: v1.0.72
|
||||
ref: v1.0.89
|
||||
|
||||
dependency_overrides:
|
||||
# dartssh2:
|
||||
# path: ../dartssh2
|
||||
# fl_lib:
|
||||
# path: ../fl_lib
|
||||
# dartssh2:
|
||||
# path: ../dartssh2
|
||||
# xterm:
|
||||
# path: ../xterm.dart
|
||||
# fl_lib:
|
||||
# path: ../fl_lib
|
||||
# fl_build:
|
||||
# path: ../fl_build
|
||||
|
||||
dev_dependencies:
|
||||
flutter_native_splash: ^2.1.6
|
||||
@@ -80,10 +82,9 @@ dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
fl_build:
|
||||
# path: ../fl_build
|
||||
git:
|
||||
url: https://github.com/lppcg/fl_build.git
|
||||
ref: v1.0.34
|
||||
ref: v1.0.35
|
||||
|
||||
flutter:
|
||||
generate: true
|
||||
|
||||
Reference in New Issue
Block a user