opt.: more friendly err tip

This commit is contained in:
lollipopkit
2024-05-09 21:50:30 +08:00
parent 131ece725a
commit 7767cc4b51
17 changed files with 128 additions and 31 deletions

View File

@@ -1,3 +1,5 @@
import 'package:toolbox/core/extension/context/locale.dart';
enum ErrFrom { enum ErrFrom {
unknown, unknown,
apt, apt,
@@ -15,18 +17,35 @@ abstract class Err<T> {
final T type; final T type;
final String? message; final String? message;
String? get solution;
Err({required this.from, required this.type, this.message}); Err({required this.from, required this.type, this.message});
} }
enum SSHErrType { enum SSHErrType {
unknown, unknown,
connect, connect,
noPrivateKey; auth,
noPrivateKey,
chdir,
segements,
writeScript,
getStatus,
;
} }
class SSHErr extends Err<SSHErrType> { class SSHErr extends Err<SSHErrType> {
SSHErr({required super.type, super.message}) : super(from: ErrFrom.ssh); SSHErr({required super.type, super.message}) : super(from: ErrFrom.ssh);
@override
String? get solution => switch (type) {
SSHErrType.chdir => l10n.needHomeDir,
SSHErrType.auth => l10n.authFailTip,
SSHErrType.writeScript => l10n.writeScriptFailTip,
SSHErrType.noPrivateKey => l10n.noPrivateKeyTip,
_ => null,
};
@override @override
String toString() { String toString() {
return 'SSHErr<$type>: $message'; return 'SSHErr<$type>: $message';
@@ -49,6 +68,9 @@ class ContainerErr extends Err<ContainerErrType> {
ContainerErr({required super.type, super.message}) ContainerErr({required super.type, super.message})
: super(from: ErrFrom.docker); : super(from: ErrFrom.docker);
@override
String? get solution => null;
@override @override
String toString() { String toString() {
return 'ContainerErr<$type>: $message'; return 'ContainerErr<$type>: $message';
@@ -64,6 +86,9 @@ enum ICloudErrType {
class ICloudErr extends Err<ICloudErrType> { class ICloudErr extends Err<ICloudErrType> {
ICloudErr({required super.type, super.message}) : super(from: ErrFrom.icloud); ICloudErr({required super.type, super.message}) : super(from: ErrFrom.icloud);
@override
String? get solution => null;
@override @override
String toString() { String toString() {
return 'ICloudErr<$type>: $message'; return 'ICloudErr<$type>: $message';
@@ -79,6 +104,9 @@ enum WebdavErrType {
class WebdavErr extends Err<WebdavErrType> { class WebdavErr extends Err<WebdavErrType> {
WebdavErr({required super.type, super.message}) : super(from: ErrFrom.webdav); WebdavErr({required super.type, super.message}) : super(from: ErrFrom.webdav);
@override
String? get solution => null;
@override @override
String toString() { String toString() {
return 'WebdavErr<$type>: $message'; return 'WebdavErr<$type>: $message';
@@ -95,6 +123,9 @@ enum PveErrType {
class PveErr extends Err<PveErrType> { class PveErr extends Err<PveErrType> {
PveErr({required super.type, super.message}) : super(from: ErrFrom.status); PveErr({required super.type, super.message}) : super(from: ErrFrom.status);
@override
String? get solution => null;
@override @override
String toString() { String toString() {
return 'PveErr<$type>: $message'; return 'PveErr<$type>: $message';

View File

@@ -1,6 +1,7 @@
import 'package:dartssh2/dartssh2.dart'; import 'package:dartssh2/dartssh2.dart';
import 'package:toolbox/core/extension/context/locale.dart'; import 'package:toolbox/core/extension/context/locale.dart';
import 'package:toolbox/core/extension/listx.dart'; import 'package:toolbox/core/extension/listx.dart';
import 'package:toolbox/data/model/app/error.dart';
import 'package:toolbox/data/model/app/shell_func.dart'; import 'package:toolbox/data/model/app/shell_func.dart';
import 'package:toolbox/data/model/server/battery.dart'; import 'package:toolbox/data/model/server/battery.dart';
import 'package:toolbox/data/model/server/conn.dart'; import 'package:toolbox/data/model/server/conn.dart';
@@ -55,7 +56,7 @@ class ServerStatus {
NetSpeed netSpeed; NetSpeed netSpeed;
Temperatures temps; Temperatures temps;
SystemType system; SystemType system;
String? err; Err? err;
DiskIO diskIO; DiskIO diskIO;
List<NvidiaSmiItem>? nvidia; List<NvidiaSmiItem>? nvidia;
final List<Battery> batteries = []; final List<Battery> batteries = [];

View File

@@ -53,7 +53,7 @@ extension ServerX on Server {
case ServerConn.connecting: case ServerConn.connecting:
return l10n.serverTabConnecting; return l10n.serverTabConnecting;
case ServerConn.failed: case ServerConn.failed:
return status.err ?? l10n.serverTabFailed; return status.err != null ? l10n.viewErr : l10n.serverTabFailed;
} }
} }
} }

View File

@@ -8,6 +8,7 @@ import 'package:toolbox/core/extension/ssh_client.dart';
import 'package:toolbox/core/extension/stringx.dart'; import 'package:toolbox/core/extension/stringx.dart';
import 'package:toolbox/core/utils/ssh_auth.dart'; import 'package:toolbox/core/utils/ssh_auth.dart';
import 'package:toolbox/core/utils/platform/path.dart'; import 'package:toolbox/core/utils/platform/path.dart';
import 'package:toolbox/data/model/app/error.dart';
import 'package:toolbox/data/model/app/shell_func.dart'; import 'package:toolbox/data/model/app/shell_func.dart';
import 'package:toolbox/data/model/server/system.dart'; import 'package:toolbox/data/model/server/system.dart';
import 'package:toolbox/data/model/sftp/req.dart'; import 'package:toolbox/data/model/sftp/req.dart';
@@ -290,7 +291,7 @@ class ServerProvider extends ChangeNotifier {
} }
} catch (e) { } catch (e) {
TryLimiter.inc(sid); TryLimiter.inc(sid);
s.status.err = e.toString(); s.status.err = SSHErr(type: SSHErrType.connect, message: e.toString());
_setServerState(s, ServerConn.failed); _setServerState(s, ServerConn.failed);
/// In order to keep privacy, print [spi.name] instead of [spi.id] /// In order to keep privacy, print [spi.name] instead of [spi.id]
@@ -313,12 +314,12 @@ class ServerProvider extends ChangeNotifier {
); );
} on SSHAuthAbortError catch (e) { } on SSHAuthAbortError catch (e) {
TryLimiter.inc(sid); TryLimiter.inc(sid);
s.status.err = e.toString(); s.status.err = SSHErr(type: SSHErrType.auth, message: e.toString());
_setServerState(s, ServerConn.failed); _setServerState(s, ServerConn.failed);
return; return;
} on SSHAuthFailError catch (e) { } on SSHAuthFailError catch (e) {
TryLimiter.inc(sid); TryLimiter.inc(sid);
s.status.err = e.toString(); s.status.err = SSHErr(type: SSHErrType.auth, message: e.toString());
_setServerState(s, ServerConn.failed); _setServerState(s, ServerConn.failed);
return; return;
} catch (e) { } catch (e) {
@@ -344,11 +345,14 @@ class ServerProvider extends ChangeNotifier {
if (err != null) { if (err != null) {
throw err; throw err;
} }
} catch (e) { } catch (ee) {
TryLimiter.inc(sid); TryLimiter.inc(sid);
s.status.err = e.toString(); s.status.err = SSHErr(
type: SSHErrType.writeScript,
message: '$e\n\n$ee',
);
_setServerState(s, ServerConn.failed); _setServerState(s, ServerConn.failed);
Loggers.app.warning('Write script to ${spi.name} by sftp', e); Loggers.app.warning('Write script to ${spi.name} by sftp', ee);
return; return;
} finally { } finally {
if (await file.exists()) await file.delete(); if (await file.exists()) await file.delete();
@@ -379,13 +383,16 @@ class ServerProvider extends ChangeNotifier {
} }
} }
TryLimiter.inc(sid); TryLimiter.inc(sid);
s.status.err = 'Seperate segments failed, raw:\n$raw'; s.status.err = SSHErr(
type: SSHErrType.segements,
message: 'Seperate segments failed, raw:\n$raw',
);
_setServerState(s, ServerConn.failed); _setServerState(s, ServerConn.failed);
return; return;
} }
} catch (e) { } catch (e) {
TryLimiter.inc(sid); TryLimiter.inc(sid);
s.status.err = e.toString(); s.status.err = SSHErr(type: SSHErrType.getStatus, message: e.toString());
_setServerState(s, ServerConn.failed); _setServerState(s, ServerConn.failed);
Loggers.app.warning('Get status from ${spi.name} failed', e); Loggers.app.warning('Get status from ${spi.name} failed', e);
return; return;
@@ -395,10 +402,17 @@ class ServerProvider extends ChangeNotifier {
final customCmdLen = spi.custom?.cmds?.length ?? 0; final customCmdLen = spi.custom?.cmds?.length ?? 0;
if (!systemType.isSegmentsLenMatch(segments.length - customCmdLen)) { if (!systemType.isSegmentsLenMatch(segments.length - customCmdLen)) {
TryLimiter.inc(sid); TryLimiter.inc(sid);
if (raw.contains('Could not chdir to home directory /var/services/')) {
s.status.err = SSHErr(type: SSHErrType.chdir, message: raw);
_setServerState(s, ServerConn.failed);
return;
}
final expected = systemType.segmentsLen; final expected = systemType.segmentsLen;
final actual = segments.length; final actual = segments.length;
final err = 'Segments: expect $expected, got $actual, raw:\n\n$raw'; s.status.err = SSHErr(
s.status.err = err; type: SSHErrType.segements,
message: 'Segments: expect $expected, got $actual, raw:\n\n$raw',
);
_setServerState(s, ServerConn.failed); _setServerState(s, ServerConn.failed);
return; return;
} }
@@ -418,7 +432,10 @@ class ServerProvider extends ChangeNotifier {
); );
} catch (e, trace) { } catch (e, trace) {
TryLimiter.inc(sid); TryLimiter.inc(sid);
s.status.err = 'Parse failed: $e\n\n$raw'; s.status.err = SSHErr(
type: SSHErrType.getStatus,
message: 'Parse failed: $e\n\n$raw',
);
_setServerState(s, ServerConn.failed); _setServerState(s, ServerConn.failed);
Loggers.parse.warning('Server status', e, trace); Loggers.parse.warning('Server status', e, trace);
return; return;

View File

@@ -13,6 +13,7 @@
"alterUrl": "Url ändern", "alterUrl": "Url ändern",
"askContinue": "{msg}. Weiter?", "askContinue": "{msg}. Weiter?",
"attention": "Achtung", "attention": "Achtung",
"authFailTip": "Authentifizierung fehlgeschlagen, bitte überprüfen Sie, ob das Passwort/Schlüssel/Host/Benutzer usw. falsch sind.",
"authRequired": "Autorisierung erforderlich", "authRequired": "Autorisierung erforderlich",
"auto": "System folgen", "auto": "System folgen",
"autoBackupConflict": "Es kann nur eine automatische Sicherung gleichzeitig aktiviert werden.", "autoBackupConflict": "Es kann nur eine automatische Sicherung gleichzeitig aktiviert werden.",
@@ -166,6 +167,7 @@
"moveOutServerFuncBtnsHelp": "Ein: kann unter jeder Karte auf der Registerkarte \"Server\" angezeigt werden. Aus: kann oben auf der Seite \"Serverdetails\" angezeigt werden.", "moveOutServerFuncBtnsHelp": "Ein: kann unter jeder Karte auf der Registerkarte \"Server\" angezeigt werden. Aus: kann oben auf der Seite \"Serverdetails\" angezeigt werden.",
"ms": "ms", "ms": "ms",
"name": "Name", "name": "Name",
"needHomeDir": "Wenn Sie ein Synology-Benutzer sind, [sehen Sie hier](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Benutzer anderer Systeme müssen suchen, wie man ein Home-Verzeichnis erstellt.",
"needRestart": "App muss neugestartet werden", "needRestart": "App muss neugestartet werden",
"net": "Netz", "net": "Netz",
"netViewType": "Netzwerkansicht Typ", "netViewType": "Netzwerkansicht Typ",
@@ -175,6 +177,7 @@
"noLineChart": "Verwenden Sie keine Liniendiagramme", "noLineChart": "Verwenden Sie keine Liniendiagramme",
"noNotiPerm": "Keine Benachrichtigungsrechte, möglicherweise keine Fortschrittsanzeige beim Herunterladen von App-Updates.", "noNotiPerm": "Keine Benachrichtigungsrechte, möglicherweise keine Fortschrittsanzeige beim Herunterladen von App-Updates.",
"noOptions": "Keine Optionen verfügbar", "noOptions": "Keine Optionen verfügbar",
"noPrivateKeyTip": "Der private Schlüssel existiert nicht, möglicherweise wurde er gelöscht oder es liegt ein Konfigurationsfehler vor.",
"noPromptAgain": "Nicht mehr nachfragen", "noPromptAgain": "Nicht mehr nachfragen",
"noResult": "Kein Ergebnis", "noResult": "Kein Ergebnis",
"noSavedPrivateKey": "Keine gespeicherten Private Keys", "noSavedPrivateKey": "Keine gespeicherten Private Keys",
@@ -325,5 +328,6 @@
"webdavSettingEmpty": "Webdav-Einstellungen sind leer", "webdavSettingEmpty": "Webdav-Einstellungen sind leer",
"whenOpenApp": "Beim Öffnen der App", "whenOpenApp": "Beim Öffnen der App",
"willTakEeffectImmediately": "Wird sofort angewendet", "willTakEeffectImmediately": "Wird sofort angewendet",
"write": "Schreiben" "write": "Schreiben",
"writeScriptFailTip": "Das Schreiben des Skripts ist fehlgeschlagen, möglicherweise aufgrund fehlender Berechtigungen oder das Verzeichnis existiert nicht."
} }

View File

@@ -13,6 +13,7 @@
"alterUrl": "Alter url", "alterUrl": "Alter url",
"askContinue": "{msg}. Continue?", "askContinue": "{msg}. Continue?",
"attention": "Attention", "attention": "Attention",
"authFailTip": "Authentication failed, please check if the password/key/host/user, etc., are incorrect.",
"authRequired": "Auth required", "authRequired": "Auth required",
"auto": "Auto", "auto": "Auto",
"autoBackupConflict": "Only one automatic backup can be turned on at the same time.", "autoBackupConflict": "Only one automatic backup can be turned on at the same time.",
@@ -166,6 +167,7 @@
"moveOutServerFuncBtnsHelp": "On: can be displayed below each card on the Server Tab page. Off: can be displayed at the top of the Server Details page.", "moveOutServerFuncBtnsHelp": "On: can be displayed below each card on the Server Tab page. Off: can be displayed at the top of the Server Details page.",
"ms": "ms", "ms": "ms",
"name": "Name", "name": "Name",
"needHomeDir": "If you are a Synology user, [see here](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Users of other systems need to search for how to create a home directory.",
"needRestart": "Need to restart app", "needRestart": "Need to restart app",
"net": "Net", "net": "Net",
"netViewType": "Net view type", "netViewType": "Net view type",
@@ -175,6 +177,7 @@
"noLineChart": "Do not use line charts", "noLineChart": "Do not use line charts",
"noNotiPerm": "No notification permissions, possibly no progress indication when downloading app updates.", "noNotiPerm": "No notification permissions, possibly no progress indication when downloading app updates.",
"noOptions": "No options", "noOptions": "No options",
"noPrivateKeyTip": "The private key does not exist, it may have been deleted or there is a configuration error.",
"noPromptAgain": "Do not prompt again", "noPromptAgain": "Do not prompt again",
"noResult": "No result", "noResult": "No result",
"noSavedPrivateKey": "No saved private keys.", "noSavedPrivateKey": "No saved private keys.",
@@ -325,5 +328,6 @@
"webdavSettingEmpty": "Webdav setting is empty", "webdavSettingEmpty": "Webdav setting is empty",
"whenOpenApp": "When opening the app", "whenOpenApp": "When opening the app",
"willTakEeffectImmediately": "Will take effect immediately", "willTakEeffectImmediately": "Will take effect immediately",
"write": "Write" "write": "Write",
"writeScriptFailTip": "Writing to the script failed, possibly due to lack of permissions or the directory does not exist."
} }

View File

@@ -13,6 +13,7 @@
"alterUrl": "URL alternativa", "alterUrl": "URL alternativa",
"askContinue": "{msg}, ¿continuar?", "askContinue": "{msg}, ¿continuar?",
"attention": "Atención", "attention": "Atención",
"authFailTip": "La autenticación ha fallado, por favor verifica si la contraseña/llave/host/usuario, etc., son incorrectos.",
"authRequired": "Autenticación requerida", "authRequired": "Autenticación requerida",
"auto": "Automático", "auto": "Automático",
"autoBackupConflict": "Solo se puede activar una copia de seguridad automática a la vez", "autoBackupConflict": "Solo se puede activar una copia de seguridad automática a la vez",
@@ -166,6 +167,7 @@
"moveOutServerFuncBtnsHelp": "Activado: se mostrará debajo de cada tarjeta en la página de servidores. Desactivado: se mostrará en la parte superior de los detalles del servidor.", "moveOutServerFuncBtnsHelp": "Activado: se mostrará debajo de cada tarjeta en la página de servidores. Desactivado: se mostrará en la parte superior de los detalles del servidor.",
"ms": "milisegundos", "ms": "milisegundos",
"name": "Nombre", "name": "Nombre",
"needHomeDir": "Si eres usuario de Synology, [consulta aquí](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Los usuarios de otros sistemas deben buscar cómo crear un directorio home.",
"needRestart": "Necesita reiniciar la app", "needRestart": "Necesita reiniciar la app",
"net": "Red", "net": "Red",
"netViewType": "Tipo de vista de red", "netViewType": "Tipo de vista de red",
@@ -175,6 +177,7 @@
"noLineChart": "No utilice gráficos de líneas", "noLineChart": "No utilice gráficos de líneas",
"noNotiPerm": "Sin permisos de notificación, posiblemente sin indicación de progreso al descargar actualizaciones de la aplicación.", "noNotiPerm": "Sin permisos de notificación, posiblemente sin indicación de progreso al descargar actualizaciones de la aplicación.",
"noOptions": "Sin opciones disponibles", "noOptions": "Sin opciones disponibles",
"noPrivateKeyTip": "La clave privada no existe, puede haber sido eliminada o hay un error de configuración.",
"noPromptAgain": "No volver a preguntar", "noPromptAgain": "No volver a preguntar",
"noResult": "Sin resultados", "noResult": "Sin resultados",
"noSavedPrivateKey": "No hay llaves privadas guardadas.", "noSavedPrivateKey": "No hay llaves privadas guardadas.",
@@ -325,5 +328,6 @@
"webdavSettingEmpty": "La configuración de Webdav está vacía", "webdavSettingEmpty": "La configuración de Webdav está vacía",
"whenOpenApp": "Al abrir la App", "whenOpenApp": "Al abrir la App",
"willTakEeffectImmediately": "Los cambios tendrán efecto inmediatamente", "willTakEeffectImmediately": "Los cambios tendrán efecto inmediatamente",
"write": "Escribir" "write": "Escribir",
"writeScriptFailTip": "La escritura en el script falló, posiblemente por falta de permisos o porque el directorio no existe."
} }

View File

@@ -13,6 +13,7 @@
"alterUrl": "Modifier l'URL", "alterUrl": "Modifier l'URL",
"askContinue": "{msg}. Continuer?", "askContinue": "{msg}. Continuer?",
"attention": "Attention", "attention": "Attention",
"authFailTip": "Échec de l'authentification, veuillez vérifier si le mot de passe/clé/hôte/utilisateur, etc., sont incorrects.",
"authRequired": "Authentification requise", "authRequired": "Authentification requise",
"auto": "Auto", "auto": "Auto",
"autoBackupConflict": "Une seule sauvegarde automatique peut être activée à la fois.", "autoBackupConflict": "Une seule sauvegarde automatique peut être activée à la fois.",
@@ -166,6 +167,7 @@
"moveOutServerFuncBtnsHelp": "Activé : peut être affiché sous chaque carte sur la page de l'onglet Serveur. Désactivé : peut être affiché en haut de la page Détails du serveur.", "moveOutServerFuncBtnsHelp": "Activé : peut être affiché sous chaque carte sur la page de l'onglet Serveur. Désactivé : peut être affiché en haut de la page Détails du serveur.",
"ms": "ms", "ms": "ms",
"name": "Nom", "name": "Nom",
"needHomeDir": "Si vous êtes utilisateur de Synology, [voir ici](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Les utilisateurs d'autres systèmes doivent rechercher comment créer un répertoire personnel.",
"needRestart": "Redémarrage de l'application nécessaire", "needRestart": "Redémarrage de l'application nécessaire",
"net": "Réseau", "net": "Réseau",
"netViewType": "Type de vue réseau", "netViewType": "Type de vue réseau",
@@ -175,6 +177,7 @@
"noLineChart": "N'utilisez pas de graphiques en ligne", "noLineChart": "N'utilisez pas de graphiques en ligne",
"noNotiPerm": "Pas de permissions de notification, il est possible qu'il n'y ait pas de notification de progression lors du téléchargement des mises à jour des applications.", "noNotiPerm": "Pas de permissions de notification, il est possible qu'il n'y ait pas de notification de progression lors du téléchargement des mises à jour des applications.",
"noOptions": "Aucune option", "noOptions": "Aucune option",
"noPrivateKeyTip": "La clé privée n'existe pas, elle a peut-être été supprimée ou il y a une erreur de configuration.",
"noPromptAgain": "Ne plus demander", "noPromptAgain": "Ne plus demander",
"noResult": "Aucun résultat", "noResult": "Aucun résultat",
"noSavedPrivateKey": "Aucune clé privée enregistrée.", "noSavedPrivateKey": "Aucune clé privée enregistrée.",
@@ -325,5 +328,6 @@
"webdavSettingEmpty": "La configuration Webdav est vide", "webdavSettingEmpty": "La configuration Webdav est vide",
"whenOpenApp": "À l'ouverture de l'application", "whenOpenApp": "À l'ouverture de l'application",
"willTakEeffectImmediately": "Prendra effet immédiatement", "willTakEeffectImmediately": "Prendra effet immédiatement",
"write": "Écrire" "write": "Écrire",
"writeScriptFailTip": "L'écriture du script a échoué, peut-être en raison d'un manque de permissions ou parce que le répertoire n'existe pas."
} }

View File

@@ -13,6 +13,7 @@
"alterUrl": "Alter url", "alterUrl": "Alter url",
"askContinue": "{msg}, lanjutkan?", "askContinue": "{msg}, lanjutkan?",
"attention": "Perhatian", "attention": "Perhatian",
"authFailTip": "Otentikasi gagal, silakan periksa apakah kata sandi/kunci/host/pengguna, dll, salah.",
"authRequired": "Auth diperlukan", "authRequired": "Auth diperlukan",
"auto": "Auto", "auto": "Auto",
"autoBackupConflict": "Hanya satu pencadangan otomatis yang dapat diaktifkan pada saat yang bersamaan.", "autoBackupConflict": "Hanya satu pencadangan otomatis yang dapat diaktifkan pada saat yang bersamaan.",
@@ -166,6 +167,7 @@
"moveOutServerFuncBtnsHelp": "Aktif: dapat ditampilkan di bawah setiap kartu pada halaman Tab Server. Nonaktif: dapat ditampilkan di bagian atas halaman Rincian Server.", "moveOutServerFuncBtnsHelp": "Aktif: dapat ditampilkan di bawah setiap kartu pada halaman Tab Server. Nonaktif: dapat ditampilkan di bagian atas halaman Rincian Server.",
"ms": "MS", "ms": "MS",
"name": "Nama", "name": "Nama",
"needHomeDir": "Jika Anda pengguna Synology, [lihat di sini](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Pengguna sistem lain perlu mencari cara membuat direktori home.",
"needRestart": "Perlu memulai ulang aplikasi", "needRestart": "Perlu memulai ulang aplikasi",
"net": "Net", "net": "Net",
"netViewType": "Jenis tampilan bersih", "netViewType": "Jenis tampilan bersih",
@@ -175,6 +177,7 @@
"noLineChart": "Jangan gunakan grafik garis", "noLineChart": "Jangan gunakan grafik garis",
"noNotiPerm": "Tidak ada izin notifikasi, mungkin tidak ada indikasi kemajuan saat mengunduh pembaruan aplikasi.", "noNotiPerm": "Tidak ada izin notifikasi, mungkin tidak ada indikasi kemajuan saat mengunduh pembaruan aplikasi.",
"noOptions": "Tidak ada opsi", "noOptions": "Tidak ada opsi",
"noPrivateKeyTip": "Kunci privat tidak ada, mungkin telah dihapus atau ada kesalahan konfigurasi.",
"noPromptAgain": "Jangan tanya lagi", "noPromptAgain": "Jangan tanya lagi",
"noResult": "Tidak ada hasil", "noResult": "Tidak ada hasil",
"noSavedPrivateKey": "Tidak ada kunci pribadi yang disimpan.", "noSavedPrivateKey": "Tidak ada kunci pribadi yang disimpan.",
@@ -325,5 +328,6 @@
"webdavSettingEmpty": "Pengaturan webdav kosong", "webdavSettingEmpty": "Pengaturan webdav kosong",
"whenOpenApp": "Saat membuka aplikasi", "whenOpenApp": "Saat membuka aplikasi",
"willTakEeffectImmediately": "Akan segera berlaku", "willTakEeffectImmediately": "Akan segera berlaku",
"write": "Tulis" "write": "Tulis",
"writeScriptFailTip": "Penulisan ke skrip gagal, mungkin karena tidak ada izin atau direktori tidak ada."
} }

View File

@@ -13,6 +13,7 @@
"alterUrl": "代替リンク", "alterUrl": "代替リンク",
"askContinue": "{msg}、続行しますか?", "askContinue": "{msg}、続行しますか?",
"attention": "注意", "attention": "注意",
"authFailTip": "認証に失敗しました。パスワード/鍵/ホスト/ユーザーなどが間違っていないか確認してください。",
"authRequired": "認証が必要", "authRequired": "認証が必要",
"auto": "自動", "auto": "自動",
"autoBackupConflict": "自動バックアップは一度に一つしか開始できません", "autoBackupConflict": "自動バックアップは一度に一つしか開始できません",
@@ -166,6 +167,7 @@
"moveOutServerFuncBtnsHelp": "有効にする:サーバータブの各カードの下に表示されます。無効にする:サーバーの詳細ページの上部に表示されます。", "moveOutServerFuncBtnsHelp": "有効にする:サーバータブの各カードの下に表示されます。無効にする:サーバーの詳細ページの上部に表示されます。",
"ms": "ミリ秒", "ms": "ミリ秒",
"name": "名前", "name": "名前",
"needHomeDir": "Synologyユーザーの場合は、[こちらをご覧ください](https://kb.synology.com/DSM/tutorial/user_enable_home_service)。他のシステムのユーザーは、ホームディレクトリの作成方法を検索する必要があります。",
"needRestart": "アプリを再起動する必要があります", "needRestart": "アプリを再起動する必要があります",
"net": "ネットワーク", "net": "ネットワーク",
"netViewType": "ネットワークビュータイプ", "netViewType": "ネットワークビュータイプ",
@@ -175,6 +177,7 @@
"noLineChart": "折れ線グラフを使用しない", "noLineChart": "折れ線グラフを使用しない",
"noNotiPerm": "通知の権限がないため、アプリの更新のダウンロード中に進行状況が表示されない場合があります。", "noNotiPerm": "通知の権限がないため、アプリの更新のダウンロード中に進行状況が表示されない場合があります。",
"noOptions": "選択肢がありません", "noOptions": "選択肢がありません",
"noPrivateKeyTip": "私有鍵が存在しません。削除されたか、設定ミスがある可能性があります。",
"noPromptAgain": "再度確認しない", "noPromptAgain": "再度確認しない",
"noResult": "結果なし", "noResult": "結果なし",
"noSavedPrivateKey": "保存されたプライベートキーがありません。", "noSavedPrivateKey": "保存されたプライベートキーがありません。",
@@ -325,5 +328,6 @@
"webdavSettingEmpty": "Webdavの設定が空です", "webdavSettingEmpty": "Webdavの設定が空です",
"whenOpenApp": "アプリを開くとき", "whenOpenApp": "アプリを開くとき",
"willTakEeffectImmediately": "変更は即座に有効になります", "willTakEeffectImmediately": "変更は即座に有効になります",
"write": "書き込み" "write": "書き込み",
"writeScriptFailTip": "スクリプトの書き込みに失敗しました。権限がないかディレクトリが存在しない可能性があります。"
} }

View File

@@ -13,6 +13,7 @@
"alterUrl": "Url wijzigen", "alterUrl": "Url wijzigen",
"askContinue": "{msg}. Doorgaan?", "askContinue": "{msg}. Doorgaan?",
"attention": "Let op", "attention": "Let op",
"authFailTip": "Authenticatie mislukt, controleer of het wachtwoord/sleutel/host/gebruiker, enz., incorrect zijn.",
"authRequired": "Authenticatie vereist", "authRequired": "Authenticatie vereist",
"auto": "Auto", "auto": "Auto",
"autoBackupConflict": "Er kan slechts één automatische back-up tegelijk worden ingeschakeld.", "autoBackupConflict": "Er kan slechts één automatische back-up tegelijk worden ingeschakeld.",
@@ -166,6 +167,7 @@
"moveOutServerFuncBtnsHelp": "Aan: kan worden weergegeven onder elke kaart op de Server-tabbladpagina. Uit: kan worden weergegeven bovenaan de Serverdetails-pagina.", "moveOutServerFuncBtnsHelp": "Aan: kan worden weergegeven onder elke kaart op de Server-tabbladpagina. Uit: kan worden weergegeven bovenaan de Serverdetails-pagina.",
"ms": "ms", "ms": "ms",
"name": "Naam", "name": "Naam",
"needHomeDir": "Als u een Synology-gebruiker bent, [zie hier](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Gebruikers van andere systemen moeten zoeken hoe ze een home directory kunnen creëren.",
"needRestart": "App moet opnieuw worden gestart", "needRestart": "App moet opnieuw worden gestart",
"net": "Netwerk", "net": "Netwerk",
"netViewType": "Netweergavetype", "netViewType": "Netweergavetype",
@@ -174,6 +176,7 @@
"noInterface": "Geen interface", "noInterface": "Geen interface",
"noNotiPerm": "Geen meldingsmachtigingen, mogelijk geen voortgangsindicatie bij het downloaden van app-updates.", "noNotiPerm": "Geen meldingsmachtigingen, mogelijk geen voortgangsindicatie bij het downloaden van app-updates.",
"noOptions": "Geen opties", "noOptions": "Geen opties",
"noPrivateKeyTip": "De privésleutel bestaat niet, deze is mogelijk verwijderd of er is een configuratiefout.",
"noPromptAgain": "Niet meer vragen", "noPromptAgain": "Niet meer vragen",
"noResult": "Geen resultaat", "noResult": "Geen resultaat",
"noSavedPrivateKey": "Geen opgeslagen privésleutels.", "noSavedPrivateKey": "Geen opgeslagen privésleutels.",
@@ -324,5 +327,6 @@
"webdavSettingEmpty": "Webdav-instelling is leeg", "webdavSettingEmpty": "Webdav-instelling is leeg",
"whenOpenApp": "Bij het openen van de app", "whenOpenApp": "Bij het openen van de app",
"willTakEeffectImmediately": "Zal onmiddellijk van kracht worden", "willTakEeffectImmediately": "Zal onmiddellijk van kracht worden",
"write": "Schrijven" "write": "Schrijven",
"writeScriptFailTip": "Het schrijven naar het script is mislukt, mogelijk door gebrek aan rechten of omdat de map niet bestaat."
} }

View File

@@ -13,6 +13,7 @@
"alterUrl": "URL alternativa", "alterUrl": "URL alternativa",
"askContinue": "{msg}, continuar?", "askContinue": "{msg}, continuar?",
"attention": "Atenção", "attention": "Atenção",
"authFailTip": "Autenticação falhou, por favor verifique se a senha/chave/host/usuário, etc., estão incorretos.",
"authRequired": "Autenticação necessária", "authRequired": "Autenticação necessária",
"auto": "Automático", "auto": "Automático",
"autoBackupConflict": "Apenas um backup automático pode ser ativado por vez", "autoBackupConflict": "Apenas um backup automático pode ser ativado por vez",
@@ -166,6 +167,7 @@
"moveOutServerFuncBtnsHelp": "Ativado: Mostra abaixo de cada cartão na aba do servidor. Desativado: Mostra no topo da página de detalhes do servidor.", "moveOutServerFuncBtnsHelp": "Ativado: Mostra abaixo de cada cartão na aba do servidor. Desativado: Mostra no topo da página de detalhes do servidor.",
"ms": "ms", "ms": "ms",
"name": "Nome", "name": "Nome",
"needHomeDir": "Se você é usuário de Synology, [veja aqui](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Usuários de outros sistemas precisam pesquisar como criar um diretório home.",
"needRestart": "Necessita reiniciar o app", "needRestart": "Necessita reiniciar o app",
"net": "Rede", "net": "Rede",
"netViewType": "Tipo de visualização de rede", "netViewType": "Tipo de visualização de rede",
@@ -175,6 +177,7 @@
"noLineChart": "Gebruik geen lijndiagrammen", "noLineChart": "Gebruik geen lijndiagrammen",
"noNotiPerm": "Sem permissão de notificação, possivelmente sem indicação de progresso ao baixar atualizações de aplicativos.", "noNotiPerm": "Sem permissão de notificação, possivelmente sem indicação de progresso ao baixar atualizações de aplicativos.",
"noOptions": "Sem opções", "noOptions": "Sem opções",
"noPrivateKeyTip": "A chave privada não existe, pode ter sido deletada ou há um erro de configuração.",
"noPromptAgain": "Não perguntar novamente", "noPromptAgain": "Não perguntar novamente",
"noResult": "Sem resultados", "noResult": "Sem resultados",
"noSavedPrivateKey": "Nenhuma chave privada salva.", "noSavedPrivateKey": "Nenhuma chave privada salva.",
@@ -325,5 +328,6 @@
"webdavSettingEmpty": "Configurações de Webdav estão vazias", "webdavSettingEmpty": "Configurações de Webdav estão vazias",
"whenOpenApp": "Ao abrir o app", "whenOpenApp": "Ao abrir o app",
"willTakEeffectImmediately": "As alterações serão aplicadas imediatamente", "willTakEeffectImmediately": "As alterações serão aplicadas imediatamente",
"write": "Escrita" "write": "Escrita",
"writeScriptFailTip": "Falha ao escrever no script, possivelmente devido à falta de permissões ou o diretório não existe."
} }

View File

@@ -13,6 +13,7 @@
"alterUrl": "альтернативная ссылка", "alterUrl": "альтернативная ссылка",
"askContinue": "{msg}, продолжить?", "askContinue": "{msg}, продолжить?",
"attention": "внимание", "attention": "внимание",
"authFailTip": "Аутентификация не удалась, пожалуйста, проверьте, правильны ли пароль/ключ/хост/пользователь и т.д.",
"authRequired": "требуется аутентификация", "authRequired": "требуется аутентификация",
"auto": "авто", "auto": "авто",
"autoBackupConflict": "Может быть включено только одно автоматическое резервное копирование", "autoBackupConflict": "Может быть включено только одно автоматическое резервное копирование",
@@ -166,6 +167,7 @@
"moveOutServerFuncBtnsHelp": "Включено: кнопки функций сервера отображаются под каждой карточкой на вкладке сервера. Выключено: отображается в верхней части страницы деталей сервера.", "moveOutServerFuncBtnsHelp": "Включено: кнопки функций сервера отображаются под каждой карточкой на вкладке сервера. Выключено: отображается в верхней части страницы деталей сервера.",
"ms": "мс", "ms": "мс",
"name": "имя", "name": "имя",
"needHomeDir": "Если вы пользователь Synology, [смотрите здесь](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Пользователям других систем нужно искать, как создать домашний каталог.",
"needRestart": "требуется перезапуск приложения", "needRestart": "требуется перезапуск приложения",
"net": "сеть", "net": "сеть",
"netViewType": "тип визуализации сети", "netViewType": "тип визуализации сети",
@@ -175,6 +177,7 @@
"noLineChart": "Não use gráficos de linha", "noLineChart": "Não use gráficos de linha",
"noNotiPerm": "Нет разрешения на уведомления, возможно отсутствие индикации прогресса при загрузке обновлений приложений.", "noNotiPerm": "Нет разрешения на уведомления, возможно отсутствие индикации прогресса при загрузке обновлений приложений.",
"noOptions": "нет доступных опций", "noOptions": "нет доступных опций",
"noPrivateKeyTip": "Приватный ключ не существует, возможно, он был удален или есть ошибка в настройках.",
"noPromptAgain": "Больше не спрашивать", "noPromptAgain": "Больше не спрашивать",
"noResult": "нет результатов", "noResult": "нет результатов",
"noSavedPrivateKey": "Нет сохраненных приватных ключей.", "noSavedPrivateKey": "Нет сохраненных приватных ключей.",
@@ -325,5 +328,6 @@
"webdavSettingEmpty": "Настройки Webdav пусты", "webdavSettingEmpty": "Настройки Webdav пусты",
"whenOpenApp": "при открытии приложения", "whenOpenApp": "при открытии приложения",
"willTakEeffectImmediately": "Изменения вступят в силу немедленно", "willTakEeffectImmediately": "Изменения вступят в силу немедленно",
"write": "запись" "write": "запись",
"writeScriptFailTip": "Запись в скрипт не удалась, возможно, из-за отсутствия прав или директории не существует."
} }

View File

@@ -13,6 +13,7 @@
"alterUrl": "备选链接", "alterUrl": "备选链接",
"askContinue": "{msg},继续吗?", "askContinue": "{msg},继续吗?",
"attention": "注意", "attention": "注意",
"authFailTip": "认证失败,请检查密码/密钥/主机/用户等是否错误",
"authRequired": "需要认证", "authRequired": "需要认证",
"auto": "自动", "auto": "自动",
"autoBackupConflict": "只能同时开启一个自动备份", "autoBackupConflict": "只能同时开启一个自动备份",
@@ -166,6 +167,7 @@
"moveOutServerFuncBtnsHelp": "开启:可以在服务器 Tab 页的每个卡片下方显示。关闭:在服务器详情页顶部显示。", "moveOutServerFuncBtnsHelp": "开启:可以在服务器 Tab 页的每个卡片下方显示。关闭:在服务器详情页顶部显示。",
"ms": "毫秒", "ms": "毫秒",
"name": "名称", "name": "名称",
"needHomeDir": "如果你是群晖用户,[看这里](https://kb.synology.cn/zh-cn/DSM/tutorial/ssh_could_not_chdir_to_home_directory)。其他系统用户需搜索如何创建家目录home directory.",
"needRestart": "需要重启 App", "needRestart": "需要重启 App",
"net": "网络", "net": "网络",
"netViewType": "网络视图类型", "netViewType": "网络视图类型",
@@ -175,6 +177,7 @@
"noLineChart": "不使用折线图", "noLineChart": "不使用折线图",
"noNotiPerm": "无通知权限可能下载App更新时无进度提示。", "noNotiPerm": "无通知权限可能下载App更新时无进度提示。",
"noOptions": "无可选项", "noOptions": "无可选项",
"noPrivateKeyTip": "私钥不存在,可能已被删除/配置错误",
"noPromptAgain": "不再提示", "noPromptAgain": "不再提示",
"noResult": "无结果", "noResult": "无结果",
"noSavedPrivateKey": "没有已保存的私钥。", "noSavedPrivateKey": "没有已保存的私钥。",
@@ -325,5 +328,6 @@
"webdavSettingEmpty": "Webdav 设置项为空", "webdavSettingEmpty": "Webdav 设置项为空",
"whenOpenApp": "当打开 App 时", "whenOpenApp": "当打开 App 时",
"willTakEeffectImmediately": "更改将会立即生效", "willTakEeffectImmediately": "更改将会立即生效",
"write": "写" "write": "写",
"writeScriptFailTip": "写入脚本失败,可能是没有权限/目录不存在等"
} }

View File

@@ -13,6 +13,7 @@
"alterUrl": "備選鏈接", "alterUrl": "備選鏈接",
"askContinue": "{msg},繼續嗎?", "askContinue": "{msg},繼續嗎?",
"attention": "注意", "attention": "注意",
"authFailTip": "認證失敗,請檢查密碼/密鑰/主機/用戶等是否錯誤。",
"authRequired": "需要認證", "authRequired": "需要認證",
"auto": "自動", "auto": "自動",
"autoBackupConflict": "只能同時開啓壹個自動備份", "autoBackupConflict": "只能同時開啓壹個自動備份",
@@ -166,6 +167,7 @@
"moveOutServerFuncBtnsHelp": "開啟:可以在服務器 Tab 頁的每個卡片下方顯示。關閉:在服務器詳情頁頂部顯示。", "moveOutServerFuncBtnsHelp": "開啟:可以在服務器 Tab 頁的每個卡片下方顯示。關閉:在服務器詳情頁頂部顯示。",
"ms": "毫秒", "ms": "毫秒",
"name": "名稱", "name": "名稱",
"needHomeDir": "如果你是群暉用戶,[看這裡](https://kb.synology.com/DSM/tutorial/user_enable_home_service)。其他系統用戶需搜索如何創建家目錄home directory。",
"needRestart": "需要重啓 App", "needRestart": "需要重啓 App",
"net": "網絡", "net": "網絡",
"netViewType": "網絡視圖類型", "netViewType": "網絡視圖類型",
@@ -175,6 +177,7 @@
"noLineChart": "不使用折線圖", "noLineChart": "不使用折線圖",
"noNotiPerm": "無通知權限,可能在下載應用程式更新時沒有進度提示。", "noNotiPerm": "無通知權限,可能在下載應用程式更新時沒有進度提示。",
"noOptions": "無可選項", "noOptions": "無可選項",
"noPrivateKeyTip": "私鑰不存在,可能已被刪除/配置錯誤。",
"noPromptAgain": "不再提示", "noPromptAgain": "不再提示",
"noResult": "無結果", "noResult": "無結果",
"noSavedPrivateKey": "沒有已保存的私鑰。", "noSavedPrivateKey": "沒有已保存的私鑰。",
@@ -325,5 +328,6 @@
"webdavSettingEmpty": "Webdav 設置項爲空", "webdavSettingEmpty": "Webdav 設置項爲空",
"whenOpenApp": "當打開 App 時", "whenOpenApp": "當打開 App 時",
"willTakEeffectImmediately": "更改將會立即生效", "willTakEeffectImmediately": "更改將會立即生效",
"write": "写" "write": "写",
"writeScriptFailTip": "寫入腳本失敗,可能是沒有權限/目錄不存在等。"
} }

View File

@@ -19,6 +19,7 @@ import 'package:toolbox/data/res/color.dart';
import 'package:toolbox/data/res/provider.dart'; import 'package:toolbox/data/res/provider.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:toolbox/data/res/store.dart';
import 'package:toolbox/view/widget/auto_hide.dart'; import 'package:toolbox/view/widget/auto_hide.dart';
import 'package:toolbox/view/widget/markdown.dart';
import 'package:toolbox/view/widget/percent_circle.dart'; import 'package:toolbox/view/widget/percent_circle.dart';
import '../../../core/route.dart'; import '../../../core/route.dart';
@@ -516,21 +517,25 @@ class _ServerPageState extends State<ServerPage>
_showFailReason(s.status); _showFailReason(s.status);
}, },
child: Text( child: Text(
hasErr ? l10n.viewErr : s.getTopRightStr(s.spi), s.getTopRightStr(s.spi),
style: UIs.text13Grey, style: UIs.text13Grey,
), ),
); );
} }
void _showFailReason(ServerStatus ss) { void _showFailReason(ServerStatus ss) {
final md = '''
${ss.err?.solution ?? l10n.unknown}
```sh
${ss.err?.message ?? l10n.unknownError}
''';
context.showRoundDialog( context.showRoundDialog(
title: Text(l10n.error), title: Text(l10n.error),
child: SingleChildScrollView( child: SingleChildScrollView(child: SimpleMarkdown(data: md)),
child: Text(ss.err ?? l10n.unknownError),
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Shares.copy(ss.err ?? l10n.unknownError), onPressed: () => Shares.copy(md),
child: Text(l10n.copy), child: Text(l10n.copy),
) )
], ],

View File

@@ -41,7 +41,6 @@ final class _AutoHideState extends State<AutoHide> {
void _setupTimer() { void _setupTimer() {
_timer?.cancel(); _timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 3), (_) { _timer = Timer.periodic(const Duration(seconds: 3), (_) {
debugPrint('[AutoHideFab._timer] trigger timer');
if (_isScrolling) return; if (_isScrolling) return;
if (!_visible) return; if (!_visible) return;
if (!widget.controller.positions.any((e) => e.maxScrollExtent >= 0)) { if (!widget.controller.positions.any((e) => e.maxScrollExtent >= 0)) {